From f1ea3a1428afdec4500a75e4908963fe10b4fb4a Mon Sep 17 00:00:00 2001 From: DS Software Date: Sun, 2 Oct 2022 01:12:35 +0400 Subject: [PATCH 01/18] Fixed the ip verify issue --- api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.php b/api.php index a845680..70a8e74 100644 --- a/api.php +++ b/api.php @@ -337,7 +337,7 @@ function uniqidReal($length = 16) { $timestamp = time(); $session_id = hash('sha256', $rsid . "_" . $timestamp . "_" . $_SERVER['REMOTE_ADDR'] . "_" . $service_key); - $ip_verify = hash("sha512", "{$user_info['SLID']}_{$user_info['user_id']}_{$user_info['user_ip']}_$service_key"); + $ip_verify = hash("sha512", "{$user_info['SLID']}_{$user_info['user_id']}_{$_SERVER['REMOTE_ADDR']}_$service_key"); setcookie("user_id", $log_user_id, time() + 2678400, $domain_name); setcookie("email", $login, time() + 2678400, $domain_name); From 4c5b12a0c829b0dbfd6c29cf73d4386c5ea9573b Mon Sep 17 00:00:00 2001 From: DS Software Date: Sun, 2 Oct 2022 01:34:41 +0400 Subject: [PATCH 02/18] Fixed issue of being unable to access API by cross origin requests Closes #59 --- api.php | 1 + 1 file changed, 1 insertion(+) diff --git a/api.php b/api.php index 70a8e74..df23e67 100644 --- a/api.php +++ b/api.php @@ -3,6 +3,7 @@ require_once 'database.php'; header('Content-Type: application/json'); +header('Access-Control-Allow-Origin: *'); function returnError($message) { $error = array( From e1dbd8f62d6c46291db2caac1071c900a0268036 Mon Sep 17 00:00:00 2001 From: DS Software Date: Sun, 2 Oct 2022 01:39:31 +0400 Subject: [PATCH 03/18] Added some features Added project bans Moved easylogin check to API Now hovering over the project's name shows its ID --- api.php | 56 ++++++++++++++++++++++++++++++++++++++++-- apps/index.php | 2 +- database.php | 32 ++++++++++++++++++------ easylogin_accept.php | 42 ++++++++++++++------------------ external_auth.php | 1 + home.php | 58 ++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 155 insertions(+), 36 deletions(-) diff --git a/api.php b/api.php index df23e67..9047cfd 100644 --- a/api.php +++ b/api.php @@ -868,7 +868,7 @@ function uniqidReal($length = 16) { $ua = json_encode($ua); - $session_link = base64_encode($login_site . "/easylogin_accept.php?session_id=" . $session . "&session_ver=" . $session_ver . "&user_agent=" . base64_encode($ua) . "&user_agent_ver=" . hash("sha256", $ua . "_" . $service_key)); + $session_link = base64_encode($login_site . "/easylogin_accept.php?session_id=" . $session . "&session_ver=" . $session_ver . "&user_agent=" . urlencode(base64_encode($ua)) . "&user_agent_ver=" . hash("sha256", $ua . "_" . $service_key)); $session_qr = "libs/gen_2fa_qr.php?method=EasyLoginSession&session=" . $session_link; @@ -1452,6 +1452,29 @@ function uniqidReal($length = 16) { echo(json_encode($return)); die(); } + + if ($method == "checkELInfo") { + $useragent_encoded = $_GET['user_agent']; + $useragent_ver = $_GET['user_agent_ver']; + + $true_ver = hash("sha256", base64_decode($useragent_encoded) . "_" . $service_key); + + if($true_ver == $useragent_ver){ + $user_agent = json_decode(base64_decode($useragent_encoded), true); + + $return = array( + 'result' => "OK", + 'browser' => $user_agent['browser'], + 'version' => $user_agent['version'], + 'platform' => $user_agent['platform'], + 'ip' => $user_agent['ip'] + ); + echo(json_encode($return)); + die(); + } + + returnError("INVALID_USER_AGENT"); + } } if ($section == "integration" && $token_scopes['profile_management']) { @@ -1666,7 +1689,8 @@ function uniqidReal($length = 16) { 'redirect_uri' => $project['redirect_uri'], 'verified' => $project['verified'], 'owner_id' => $project['owner_id'], - 'enabled' => $project['enabled'] + 'enabled' => $project['enabled'], + 'banned' => $project['banned'] ); echo(json_encode($return)); die(); @@ -1864,6 +1888,34 @@ function uniqidReal($length = 16) { echo(json_encode($return)); die(); } + + if ($method == "banProject") { + $project = $login_db->getAdminProjectInfo($_REQUEST['project_id']); + if (!$project["exists"]) { + returnError("UNKNOWN_PROJECT"); + } + $login_db->banProject($project['project_id']); + $return = array( + 'result' => "OK", + 'description' => 'Success' + ); + echo(json_encode($return)); + die(); + } + + if ($method == "unbanProject") { + $project = $login_db->getAdminProjectInfo($_REQUEST['project_id']); + if (!$project["exists"]) { + returnError("UNKNOWN_PROJECT"); + } + $login_db->unbanProject($project['project_id']); + $return = array( + 'result' => "OK", + 'description' => 'Success' + ); + echo(json_encode($return)); + die(); + } } if ($section == "authentication") { diff --git a/apps/index.php b/apps/index.php index ab77a2f..4644474 100644 --- a/apps/index.php +++ b/apps/index.php @@ -81,7 +81,7 @@ function loadUserProjects(){ container.innerHTML += "
"; let handler = document.getElementById(id); handler.textContent = element.project_name; - if(element.enabled == 0){ + if(element.enabled == 0 || element.banned == 1){ handler.textContent += " [ID: " + element.project_id + "]"; } diff --git a/database.php b/database.php index c362377..2a87617 100644 --- a/database.php +++ b/database.php @@ -238,16 +238,17 @@ public function deleteProject($project_id){ public function getUserProjects($owner_id){ $login_db = $this->ldb; $owner_id = $login_db->real_escape_string($owner_id); - $req = "SELECT `project_id`, `project_name`, `enabled` FROM `projects` WHERE `owner_id`='$owner_id'"; + $req = "SELECT `project_id`, `project_name`, `enabled`, `banned` FROM `projects` WHERE `owner_id`='$owner_id'"; $statement = $login_db->prepare($req); $statement->execute(); - $statement->bind_result($project_id, $project_name, $enabled); + $statement->bind_result($project_id, $project_name, $enabled, $banned); $projects = []; while ($statement->fetch()) { $projects[$project_id] = array( "project_id" => $project_id, "project_name" => $project_name, - "enabled" => $enabled + "enabled" => $enabled, + "banned" => $banned ); } return $projects; @@ -267,7 +268,7 @@ public function countUserProjects($owner_id){ public function getProjectInfo($project_id){ $login_db = $this->ldb; $project_id = $login_db->real_escape_string($project_id); - $req = "SELECT `project_id`, `project_name`, `redirect_uri`, `secret_key`, `public_key`, `owner_id`, `verified` FROM `projects` WHERE `project_id`='$project_id' AND `enabled`=1"; + $req = "SELECT `project_id`, `project_name`, `redirect_uri`, `secret_key`, `public_key`, `owner_id`, `verified` FROM `projects` WHERE `project_id`='$project_id' AND `enabled`=1 AND `banned`=0"; $statement = $login_db->prepare($req); $statement->execute(); $statement->bind_result($project_id, $project_name, $redirect_uri, $secret_key, $public_key, $owner_id, $verified); @@ -289,10 +290,10 @@ public function getProjectInfo($project_id){ public function getAdminProjectInfo($project_id){ $login_db = $this->ldb; $project_id = $login_db->real_escape_string($project_id); - $req = "SELECT `project_id`, `project_name`, `redirect_uri`, `owner_id`, `verified`, `enabled` FROM `projects` WHERE `project_id`='$project_id'"; + $req = "SELECT `project_id`, `project_name`, `redirect_uri`, `owner_id`, `verified`, `enabled`, `banned` FROM `projects` WHERE `project_id`='$project_id'"; $statement = $login_db->prepare($req); $statement->execute(); - $statement->bind_result($project_id, $project_name, $redirect_uri, $owner_id, $verified, $enabled); + $statement->bind_result($project_id, $project_name, $redirect_uri, $owner_id, $verified, $enabled, $banned); $project["exists"] = false; if($statement->fetch()){ $project = array( @@ -302,6 +303,7 @@ public function getAdminProjectInfo($project_id){ "owner_id" => $owner_id, "verified" => $verified, "enabled" => $enabled, + "banned" => $banned, "exists" => true ); } @@ -387,7 +389,7 @@ public function changeProjectName($project_id, $name){ public function getLoginProjectInfo($public_key){ $login_db = $this->ldb; $public_key = $login_db->real_escape_string($public_key); - $req = "SELECT `project_id`, `project_name`, `redirect_uri`, `secret_key`, `verified` FROM `projects` WHERE `public_key`='$public_key' AND `enabled`=1"; + $req = "SELECT `project_id`, `project_name`, `redirect_uri`, `secret_key`, `verified` FROM `projects` WHERE `public_key`='$public_key' AND `enabled`=1 AND `banned`=0"; $statement = $login_db->prepare($req); $statement->execute(); $statement->bind_result($project_id, $project_name, $redirect_uri, $secret_key, $verified); @@ -521,6 +523,22 @@ public function unbanUser($user_id){ $login_db->query($req); } + public function banProject($project_id){ + $login_db = $this->ldb; + $project_id = $login_db->real_escape_string($project_id); + + $req = "UPDATE `projects` SET `banned`='1' WHERE `project_id`='$project_id'"; + $login_db->query($req); + } + + public function unbanProject($project_id){ + $login_db = $this->ldb; + $project_id = $login_db->real_escape_string($project_id); + + $req = "UPDATE `projects` SET `banned`='0' WHERE `project_id`='$project_id'"; + $login_db->query($req); + } + public function getRequestStats(){ $login_db = $this->ldb; $req = "SELECT COUNT(*) FROM `requests`"; diff --git a/easylogin_accept.php b/easylogin_accept.php index c40140f..39df076 100644 --- a/easylogin_accept.php +++ b/easylogin_accept.php @@ -9,10 +9,6 @@ - - Беспарольный вход "); -} -else{ - $user_agent = json_decode(base64_decode($_GET['user_agent']), true); - $browser = htmlentities($user_agent['browser']); - $version = htmlentities($user_agent['version']); - $platform = htmlentities($user_agent['platform']); - $ip = htmlentities($user_agent['ip']); -} - -?> - \ No newline at end of file diff --git a/external_auth.php b/external_auth.php index 17d989a..e424e6d 100644 --- a/external_auth.php +++ b/external_auth.php @@ -165,6 +165,7 @@ function getProjectInfo(){ let name_container = document.getElementById("project_name"); name_container.textContent = project_name; + name_container.title = result.project_id; if(verified == 1){ let verification_mark = "Verified"; name_container.innerHTML = name_container.innerHTML + " " + verification_mark; diff --git a/home.php b/home.php index c99fafa..d6f90f3 100644 --- a/home.php +++ b/home.php @@ -277,7 +277,7 @@ function close_sidebar(){
- EMail Пользователя или ID + EMail / ID
@@ -352,7 +352,7 @@ function close_sidebar(){
- ID Проекта + ID
@@ -371,6 +371,13 @@ function close_sidebar(){

Управление Проектом

+ + + Заблокировать Проект +     +

+ Блокирует проект.
Пользователи не смогут войти в проект, а владелец не сможет управлять проектом.
+

Удалить Проект @@ -737,6 +744,7 @@ function admin_project_info(project_id){ owner_id.textContent = "ID Владельца: " + result.owner_id; window.admin_current_project = project_id; window.admin_project_state = result.enabled; + window.admin_project_ban = result.banned; if(window.admin_project_state != 0){ delete_project.classList.add('button-secondary'); delete_project.classList.remove('button-primary'); @@ -744,6 +752,17 @@ function admin_project_info(project_id){ restore_project.classList.add('button-secondary'); restore_project.classList.remove('button-primary'); } + + if(result.banned == 0){ + ban_project.classList.remove('button-secondary'); + ban_project.classList.add('button-primary'); + ban_project.textContent = "Заблокировать"; + } + else{ + ban_project.classList.add('button-secondary'); + ban_project.classList.remove('button-primary'); + ban_project.textContent = "Разблокировать"; + } } else{ choose_tab("admin_project"); @@ -754,6 +773,41 @@ function admin_project_info(project_id){ } } +function admin_ban_project(){ + if(window.is_admin){ + if(window.admin_project_ban == 0){ + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'api.php?section=admin&method=banProject&project_id=' + window.admin_current_project, true); + xhr.setRequestHeader("Authorization", "Bearer " + window.token); + xhr.send(); + xhr.onload = function (e) { + if (xhr.readyState == 4 && xhr.status == 200) { + let result = JSON.parse(xhr.responseText); + if(result.result == "OK"){ + alertify.notify("Действие было успешно выполнено.", 'success', 5); + admin_project_info(window.admin_current_project); + } + } + } + } + else{ + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'api.php?section=admin&method=unbanProject&project_id=' + window.admin_current_project, true); + xhr.setRequestHeader("Authorization", "Bearer " + window.token); + xhr.send(); + xhr.onload = function (e) { + if (xhr.readyState == 4 && xhr.status == 200) { + let result = JSON.parse(xhr.responseText); + if(result.result == "OK"){ + alertify.notify("Действие было успешно выполнено.", 'success', 5); + admin_project_info(window.admin_current_project); + } + } + } + } + } +} + function admin_user_info(user_email){ if(window.is_admin){ var xhr = new XMLHttpRequest(); From 7bd597bfa32733f4ea98dd02fbd4279a9d0a0668 Mon Sep 17 00:00:00 2001 From: DS Software Date: Sat, 8 Oct 2022 21:20:09 +0400 Subject: [PATCH 04/18] Updated IP Ver Code check algo --- .configuration/example_config.php | 1 + api.php | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.configuration/example_config.php b/.configuration/example_config.php index 1e50f32..26b3965 100644 --- a/.configuration/example_config.php +++ b/.configuration/example_config.php @@ -95,6 +95,7 @@ function getScopes($expl_scopes, $infinite=0, $admin_required=false){ $enable_creation = true; $int_url = $login_site . "/apps"; + $integrations_limit = 15; $allowed_admins = []; // [1 => true] ([USER_ID => true]) ?> \ No newline at end of file diff --git a/api.php b/api.php index 9047cfd..c218689 100644 --- a/api.php +++ b/api.php @@ -358,7 +358,8 @@ function uniqidReal($length = 16) { require_once 'libs' . DIRECTORY_SEPARATOR . 'email_handler.php'; $ip_ver_code = strtoupper(uniqidReal(8)); - $login_db->setIPCode($log_user_id, $ip_ver_code); + $ip_ver_code_hash = hash("sha512", "{$ip_ver_code}_{$user_info['SLID']}_$service_key"); + $login_db->setIPCode($log_user_id, $ip_ver_code_hash); $replaceArray = array( '$username' => $user_info['user_nick'] == "" ? "Неизвестный Пользователь" : $user_info['user_nick'], @@ -411,9 +412,11 @@ function uniqidReal($length = 16) { if ($verified) { if ($user_info['user_id'] == $user_id && $user_id != "") { - $true_code = $user_info['ip_ver_code']; + $true_code_hash = $user_info['ip_ver_code']; + + $new_code_hash = hash("sha512", "{$code}_{$user_info['SLID']}_$service_key"); - if ($code == $true_code) { + if ($true_code_hash == $new_code_hash) { $ip_verify = hash("sha512", "{$user_info['SLID']}_{$user_info['user_id']}_{$_SERVER['REMOTE_ADDR']}_$service_key"); setcookie("ip_verify", $ip_verify, time() + 2678400, $domain_name); @@ -1472,7 +1475,6 @@ function uniqidReal($length = 16) { echo(json_encode($return)); die(); } - returnError("INVALID_USER_AGENT"); } } @@ -1495,7 +1497,7 @@ function uniqidReal($length = 16) { if (!$enable_creation && $uinfo['verified'] != 1) { returnError("PROJECT_CREATION_WAS_DISABLED"); } - if ($login_db->countUserProjects($user_id) >= 15 && $uinfo['verified'] != 1) { + if ($login_db->countUserProjects($user_id) >= $integrations_limit && $uinfo['verified'] != 1) { returnError("REACHED_LIMIT_OF_PROJECTS"); } if (strlen($_REQUEST['name']) < 3 or strlen($_REQUEST['name']) > 32) { From bd6c2ceff1e54d6e593e1e10104b395fe554b1c2 Mon Sep 17 00:00:00 2001 From: DS Software Date: Sat, 8 Oct 2022 23:46:42 +0400 Subject: [PATCH 05/18] Added a User ID Validity check Closes #54 --- api.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/api.php b/api.php index c218689..a5f3347 100644 --- a/api.php +++ b/api.php @@ -1761,14 +1761,18 @@ function uniqidReal($length = 16) { } if ($method == "getUserInfo") { - $test_user_id = $login_db->getUIDByEMail($_REQUEST['email']); if (is_numeric($_REQUEST['email'])) { $test_user_id = $_REQUEST['email']; } - if ($test_user_id == "") { - returnError("UNKNOWN_USER"); + else{ + $test_user_id = $login_db->getUIDByEMail($_REQUEST['email']); } $user = $login_db->getUserInfo($test_user_id); + + if ($user['user_id'] == null) { + returnError("UNKNOWN_USER"); + } + $is_admin = false; if ($allowed_admins[$user['user_id']]) { $is_admin = true; From a2162a04b590e9a6e87376886c4d394c7b530030 Mon Sep 17 00:00:00 2001 From: DS Software Date: Sun, 9 Oct 2022 00:54:09 +0400 Subject: [PATCH 06/18] Fixed some issues Fixed #52 Also Fixed #54 --- api.php | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ check_user.php | 26 ++++++++++++++++++++++ database.php | 3 +++ 3 files changed, 88 insertions(+) diff --git a/api.php b/api.php index a5f3347..0fc1e4b 100644 --- a/api.php +++ b/api.php @@ -393,6 +393,65 @@ function uniqidReal($length = 16) { echo(json_encode($return)); die(); } + + if ($method == "sendIPCode") { + if (isRateExceeded($section . '-' . $method, $_SERVER['REMOTE_ADDR'], 1, 300)) { + returnError("RATE_LIMIT_EXCEEDED"); + } + + $user_id = $_COOKIE['user_id']; + $SLID = $_COOKIE['SLID']; + $email = $_COOKIE['email']; + $session = $_COOKIE['session']; + $user_ip = $_COOKIE['user_ip']; + $user_key = $_COOKIE['user_verkey']; + + $user_info = $login_db->getUserInfo($user_id); + + $verified = checkLoggedIn($user_id, $SLID, $email, $session, $user_ip, $user_key, $user_info); + + if ($verified) { + if ($user_info['is_banned'] == 1 && !$allowed_admins[$user_id]) { + $return = array( + 'result' => 'FAULT', + 'reason' => 'ACCOUNT_BANNED', + 'support' => "$support" + ); + echo(json_encode($return)); + die(); + } + + if($user_info['email_check'] != 0){ + require_once 'libs' . DIRECTORY_SEPARATOR . 'email_templates.php'; + require_once 'libs' . DIRECTORY_SEPARATOR . 'email_handler.php'; + + $ip_ver_code = strtoupper(uniqidReal(8)); + $ip_ver_code_hash = hash("sha512", "{$ip_ver_code}_{$user_info['SLID']}_$service_key"); + $login_db->setIPCode($user_id, $ip_ver_code_hash); + + $replaceArray = array( + '$username' => $user_info['user_nick'] == "" ? "Неизвестный Пользователь" : $user_info['user_nick'], + '$code' => $ip_ver_code, + '$ip' => $_SERVER['REMOTE_ADDR'] + ); + + $replaceArray = array_merge($replaceArray, $email_info); + + $email_html = strtr($NewIPEmail, $replaceArray); + $subject = strtr($messageNewIPSubject, $replaceArray); + + send_email($email_settings, $user_info['user_email'], $email_html, $subject); + } + + $return = array( + 'result' => 'OK', + 'description' => 'Success' + ); + echo(json_encode($return)); + die(); + } + returnError("WRONG_CREDENTIALS"); + } if ($method == 'verifyIP') { if (isRateExceeded($section . '-' . $method, $_SERVER['REMOTE_ADDR'], 10, 60)) { diff --git a/check_user.php b/check_user.php index 203c3a8..5b276e4 100644 --- a/check_user.php +++ b/check_user.php @@ -46,6 +46,9 @@

+
@@ -217,6 +220,29 @@ function checkIP(ip_ver_code){ } } +function resend_email(){ + xhr2.open('GET', 'api.php?section=UNAUTH&method=sendIPCode', true); + xhr2.send(); + xhr2.onload = function (e) { + let json = JSON.parse(xhr2.responseText); + if(json.result == "FAULT"){ + if(json.reason == "RATE_LIMIT_EXCEEDED"){ + window.failed_request = function(){ + resend_email(); + }; + callCaptcha(); + return; + } + else{ + alertify.notify("Во время выполнения запроса произошла ошибка!", 'error', 5); + } + } + else{ + alertify.notify("Письмо было отправлено повторно, проверьте почту.", 'success', 2); + } + } +} + //################################ function logout(){ diff --git a/database.php b/database.php index 2a87617..84c3297 100644 --- a/database.php +++ b/database.php @@ -20,6 +20,7 @@ public function getUserInfo($user_id){ $login_db = $this->ldb; $user_id = $login_db->real_escape_string($user_id); $req = "SELECT `user_id`, `user_nick`, `user_email`, `user_name`, `user_surname`, `birthday`, `verified`, `user_salt`, `password_hash`, `ip_ver_code`, `user_ip`, `api_key_seed`, `SLID`, `last_sid`, `easylogin`, `email_check`, `2fa_active`, `2fa_secret`, `2fa_disable_code`, `is_banned`, `ban_reason` FROM `users` WHERE `user_id`='$user_id'"; + $user_id = null; $statement = $login_db->prepare($req); $statement->execute(); $statement->bind_result($user_id, $user_nick, $user_email, $user_name, $user_surname, $birthday, $verified, $salt, $password_hash, $ip_ver_code, $user_ip, $api_key_seed, $SLID, $last_sid, $easylogin, $email_check, $totp_active, $totp_secret, $totp_disable_code, $is_banned, $ban_reason); @@ -269,6 +270,7 @@ public function getProjectInfo($project_id){ $login_db = $this->ldb; $project_id = $login_db->real_escape_string($project_id); $req = "SELECT `project_id`, `project_name`, `redirect_uri`, `secret_key`, `public_key`, `owner_id`, `verified` FROM `projects` WHERE `project_id`='$project_id' AND `enabled`=1 AND `banned`=0"; + $project_id = null; $statement = $login_db->prepare($req); $statement->execute(); $statement->bind_result($project_id, $project_name, $redirect_uri, $secret_key, $public_key, $owner_id, $verified); @@ -291,6 +293,7 @@ public function getAdminProjectInfo($project_id){ $login_db = $this->ldb; $project_id = $login_db->real_escape_string($project_id); $req = "SELECT `project_id`, `project_name`, `redirect_uri`, `owner_id`, `verified`, `enabled`, `banned` FROM `projects` WHERE `project_id`='$project_id'"; + $project_id = null; $statement = $login_db->prepare($req); $statement->execute(); $statement->bind_result($project_id, $project_name, $redirect_uri, $owner_id, $verified, $enabled, $banned); From 0360b9f1c1c39ed4cd2510675cb11ec05c994910 Mon Sep 17 00:00:00 2001 From: DS Software Date: Sun, 9 Oct 2022 01:17:52 +0400 Subject: [PATCH 07/18] Fixed another issue Fixed #55 --- api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api.php b/api.php index 0fc1e4b..add34c4 100644 --- a/api.php +++ b/api.php @@ -1553,10 +1553,10 @@ function uniqidReal($length = 16) { if (isRateExceeded($section . '-' . $method, $_SERVER['REMOTE_ADDR'], 5, 60)) { returnError("RATE_LIMIT_EXCEEDED"); } - if (!$enable_creation && $uinfo['verified'] != 1) { + if (!$enable_creation && $uinfo['verified'] != 1 && !$allowed_admins[$user_id]) { returnError("PROJECT_CREATION_WAS_DISABLED"); } - if ($login_db->countUserProjects($user_id) >= $integrations_limit && $uinfo['verified'] != 1) { + if ($login_db->countUserProjects($user_id) >= $integrations_limit && $uinfo['verified'] != 1 && !$allowed_admins[$user_id]) { returnError("REACHED_LIMIT_OF_PROJECTS"); } if (strlen($_REQUEST['name']) < 3 or strlen($_REQUEST['name']) > 32) { From 8e7457e39a8ed2a1992d2c01fd5bd6c9a3325b40 Mon Sep 17 00:00:00 2001 From: DS Software Date: Sat, 15 Oct 2022 22:03:59 +0400 Subject: [PATCH 08/18] Prepared some stuff for PHP 8 Changed some stuff to support PHP 8 Also, realised that CAPTCHA change is required. --- captcha/captcha.php | 8 ++-- libs/apmailer.php | 94 ++++++++++++++++++++++----------------------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/captcha/captcha.php b/captcha/captcha.php index 4634d09..1e7778e 100644 --- a/captcha/captcha.php +++ b/captcha/captcha.php @@ -34,7 +34,7 @@ function __construct(){ while(true){ $this->keystring=''; for($i=0;$i<$length;$i++){ - $this->keystring.=$allowed_symbols{random_int(0,strlen($allowed_symbols)-1)}; + $this->keystring.=$allowed_symbols[random_int(0,strlen($allowed_symbols)-1)]; } if(!preg_match('/cp|cb|ck|c6|c9|rn|rm|mm|co|do|cl|db|qp|qb|dp|ww/', $this->keystring)) break; } @@ -55,13 +55,13 @@ function __construct(){ $transparent = (imagecolorat($font, $i, 0) >> 24) == 127; if(!$reading_symbol && !$transparent){ - $font_metrics[$alphabet{$symbol}]=array('start'=>$i); + $font_metrics[$alphabet[$symbol]]=array('start'=>$i); $reading_symbol=true; continue; } if($reading_symbol && $transparent){ - $font_metrics[$alphabet{$symbol}]['end']=$i; + $font_metrics[$alphabet[$symbol]]['end']=$i; $reading_symbol=false; $symbol++; continue; @@ -80,7 +80,7 @@ function __construct(){ $odd=random_int(0,1); if($odd==0) $odd=-1; for($i=0;$i<$length;$i++){ - $m=$font_metrics[$this->keystring{$i}]; + $m=$font_metrics[$this->keystring[$i]]; $y=(($i%2)*$fluctuation_amplitude - $fluctuation_amplitude/2)*$odd + random_int(-round($fluctuation_amplitude/3), round($fluctuation_amplitude/3)) diff --git a/libs/apmailer.php b/libs/apmailer.php index fed3648..74e91e5 100644 --- a/libs/apmailer.php +++ b/libs/apmailer.php @@ -1010,53 +1010,6 @@ public function encode($str) } } - class MultiPart extends Part - { - private $boundary = null; - - private $parts = []; - - public function __construct($type) - { - parent::__construct(); - $this->setContentType('multipart/'. $type .'; boundary="'. $this->getBoundary() .'"'); - } - - public function addPart(Part $part) - { - $this->parts[] = $part; - return $this; - } - - public function getContent() - { - $result = ''; - - foreach ($this->parts as $part) { - $result .= '--'. $this->getBoundary() ."\r\n"; - $result .= (string) $part ."\r\n"; - } - - $result .= '--'. $this->getBoundary() ."--\r\n\r\n"; - - return $result; - } - - public function getBoundary() - { - if (is_null($this->boundary)) { - $this->boundary = str_pad(sha1(uniqid()), 46, '-', STR_PAD_LEFT); - } - - return $this->boundary; - } - - public function __toString() - { - return $this->headers ."\r\n". $this->getContent(); - } - } - class Part { protected $headers = null; @@ -1155,6 +1108,53 @@ private function encodeContent() return trim(chunk_split(base64_encode((string) $this->getContent()))); } } + + class MultiPart extends Part + { + private $boundary = null; + + private $parts = []; + + public function __construct($type) + { + parent::__construct(); + $this->setContentType('multipart/'. $type .'; boundary="'. $this->getBoundary() .'"'); + } + + public function addPart(Part $part) + { + $this->parts[] = $part; + return $this; + } + + public function getContent() + { + $result = ''; + + foreach ($this->parts as $part) { + $result .= '--'. $this->getBoundary() ."\r\n"; + $result .= (string) $part ."\r\n"; + } + + $result .= '--'. $this->getBoundary() ."--\r\n\r\n"; + + return $result; + } + + public function getBoundary() + { + if (is_null($this->boundary)) { + $this->boundary = str_pad(sha1(uniqid()), 46, '-', STR_PAD_LEFT); + } + + return $this->boundary; + } + + public function __toString() + { + return $this->headers ."\r\n". $this->getContent(); + } + } class Functions { From c3c36d0ae938478d46e9bb3ace0255f1ec5db448 Mon Sep 17 00:00:00 2001 From: DS Software Date: Sat, 15 Oct 2022 23:48:54 +0400 Subject: [PATCH 09/18] Changed annoying echo Cuz why not --- api.php | 130 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/api.php b/api.php index add34c4..cb0f404 100644 --- a/api.php +++ b/api.php @@ -11,7 +11,7 @@ function returnError($message) { 'reason' => $message ); - echo(json_encode($error, 1)); + echo json_encode($error, 1); die(); } @@ -232,7 +232,7 @@ function uniqidReal($length = 16) { 'token' => null ); - echo(json_encode($return, 1)); + echo json_encode($return, 1); die(); } if ($user_info['2fa_active'] == 1) { @@ -252,7 +252,7 @@ function uniqidReal($length = 16) { 'token' => null ); - echo(json_encode($return, 1)); + echo json_encode($return, 1); die(); } } @@ -280,7 +280,7 @@ function uniqidReal($length = 16) { 'token' => $access_token ); - echo(json_encode($return, 1)); + echo json_encode($return, 1); die(); } @@ -289,7 +289,7 @@ function uniqidReal($length = 16) { 'token' => $access_token ); - echo(json_encode($return, 1)); + echo json_encode($return, 1); } else{ deleteLoginCookies(); @@ -327,7 +327,7 @@ function uniqidReal($length = 16) { 'reason' => 'ACCOUNT_BANNED', 'support' => "$support" ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -390,7 +390,7 @@ function uniqidReal($length = 16) { 'description' => 'emailVerificationRequired' ); } - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -417,7 +417,7 @@ function uniqidReal($length = 16) { 'reason' => 'ACCOUNT_BANNED', 'support' => "$support" ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -447,7 +447,7 @@ function uniqidReal($length = 16) { 'result' => 'OK', 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } returnError("WRONG_CREDENTIALS"); @@ -487,7 +487,7 @@ function uniqidReal($length = 16) { 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -547,7 +547,7 @@ function uniqidReal($length = 16) { 'result' => 'OK', 'description' => 'emailVerificationRequired' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -626,7 +626,7 @@ function uniqidReal($length = 16) { 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -676,7 +676,7 @@ function uniqidReal($length = 16) { 'result' => 'OK', 'description' => 'emailVerificationRequired' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -746,7 +746,7 @@ function uniqidReal($length = 16) { 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -795,7 +795,7 @@ function uniqidReal($length = 16) { 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -847,7 +847,7 @@ function uniqidReal($length = 16) { 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -889,7 +889,7 @@ function uniqidReal($length = 16) { 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -941,7 +941,7 @@ function uniqidReal($length = 16) { 'session_verifier' => $session_ver ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -962,7 +962,7 @@ function uniqidReal($length = 16) { 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); } else{ returnError("UNAUTHORIZED"); @@ -1027,7 +1027,7 @@ function uniqidReal($length = 16) { 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } } @@ -1064,7 +1064,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'scopes' => $scope_desc ); - echo(json_encode($return)); + echo json_encode($return); die(); } if ($method == "login") { @@ -1137,7 +1137,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'redirect' => $redirect_url ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1162,7 +1162,7 @@ function uniqidReal($length = 16) { 'verified' => $project['verified'], 'fault_redirect' => $fault_redirect ); - echo(json_encode($return)); + echo json_encode($return); die(); } } @@ -1183,7 +1183,7 @@ function uniqidReal($length = 16) { 'admin' => $is_admin ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1195,7 +1195,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -1211,7 +1211,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -1226,7 +1226,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1237,7 +1237,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1261,7 +1261,7 @@ function uniqidReal($length = 16) { 'result' => "OK", "description" => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -1321,7 +1321,7 @@ function uniqidReal($length = 16) { 'description' => 'emailVerificationRequired' ); - echo(json_encode($return)); + echo json_encode($return); die(); } } @@ -1335,7 +1335,7 @@ function uniqidReal($length = 16) { 'easylogin' => $uinfo['easylogin'] ); - echo(json_encode($return)); + echo json_encode($return); die(); } } @@ -1367,7 +1367,7 @@ function uniqidReal($length = 16) { 'url' => $totp_url, 'secret' => $true_secret ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -1401,7 +1401,7 @@ function uniqidReal($length = 16) { 'description' => 'Success', 'disableCode' => $dis_code ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -1435,7 +1435,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -1457,7 +1457,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -1473,7 +1473,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } else{ @@ -1511,7 +1511,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1531,7 +1531,7 @@ function uniqidReal($length = 16) { 'platform' => $user_agent['platform'], 'ip' => $user_agent['ip'] ); - echo(json_encode($return)); + echo json_encode($return); die(); } returnError("INVALID_USER_AGENT"); @@ -1546,7 +1546,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'projects' => $projects ); - echo(json_encode($return)); + echo json_encode($return); die(); } if ($method == "createProject") { @@ -1569,7 +1569,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } if ($method == "getProjectInfo") { @@ -1586,7 +1586,7 @@ function uniqidReal($length = 16) { 'public_key' => $project['public_key'], 'verified' => $project['verified'] ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1600,7 +1600,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1614,7 +1614,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1628,7 +1628,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1647,7 +1647,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } if ($method == "delete") { @@ -1663,7 +1663,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => "Success" ); - echo(json_encode($return)); + echo json_encode($return); die(); } } @@ -1696,7 +1696,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } } @@ -1714,7 +1714,7 @@ function uniqidReal($length = 16) { 'users' => $user_st, 'projects' => $project_st ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1724,7 +1724,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1734,7 +1734,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1753,7 +1753,7 @@ function uniqidReal($length = 16) { 'enabled' => $project['enabled'], 'banned' => $project['banned'] ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1768,7 +1768,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } returnError("PROJECT_CANNOT_BE_DELETED"); @@ -1785,7 +1785,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } returnError("PROJECT_CANNOT_BE_RESTORED"); @@ -1801,7 +1801,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1815,7 +1815,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1851,7 +1851,7 @@ function uniqidReal($length = 16) { 'is_banned' => $user['is_banned'], 'ban_reason' => $user['ban_reason'] ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1868,7 +1868,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1885,7 +1885,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1902,7 +1902,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1919,7 +1919,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1936,7 +1936,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1950,7 +1950,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1964,7 +1964,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } @@ -1978,7 +1978,7 @@ function uniqidReal($length = 16) { 'result' => "OK", 'description' => 'Success' ); - echo(json_encode($return)); + echo json_encode($return); die(); } } @@ -2005,7 +2005,7 @@ function uniqidReal($length = 16) { 'description' => "VALID", 'user_info' => $user_info ); - echo(json_encode($return)); + echo json_encode($return); die(); } } From b8cad1473bffdee2925a28888e7378637e2fd0c5 Mon Sep 17 00:00:00 2001 From: Kepchyk <43721968+KepchykS@users.noreply.github.com> Date: Sat, 5 Nov 2022 14:40:54 +0700 Subject: [PATCH 10/18] Update README.md Changed links --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3eaef15..bdb7611 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,9 @@ The only file you need to modify is config.php. * $domain_name * Used to create cookies with proper path. * If you don't use root folder of your site, put the extension here: - * If you use something like `https://ds-software.xyz/login` - put `/login` here - * If you use something like `https://login.ds-software.xyz/` - leave `/` - * If you use something like `https://example.ds-software.xyz/login` - put `/login` here + * If you use something like `https://dssoftware.ru/login` - put `/login` here + * If you use something like `https://login.dssoftware.ru/` - leave `/` + * If you use something like `https://example.dssoftware.ru/login` - put `/login` here * $session_length * Defines the length of a random_session_id. If too small, RSID will duplicate. If too big, might cause some performance issues. * Optimal value - `32` @@ -53,4 +53,4 @@ There is a list of all libraries that are used in ULS. ## Contributing & Issues When contributing changes to the project, please provide as much detail on the changes. Malicious or meaningless contributions won't be accepted. Please, if you found an issue in ULS, create a GitHub issue. -Besides, you can contact DS Software team: https://ds-software.xyz/about/ +Besides, you can contact DS Software team: https://dssoftware.ru/about/ From 96c48f18293ccbde8a216920ca9a62d6c06c966d Mon Sep 17 00:00:00 2001 From: DS Software Date: Tue, 17 Jan 2023 22:50:41 +0400 Subject: [PATCH 11/18] Changed captcha to turnstile Closes #71 --- .configuration/example_config.php | 14 ++ api.php | 33 ++-- auth_manager.php | 1 + captcha/captcha.php | 231 ----------------------- captcha/fonts/.htaccess | 4 - captcha/fonts/palatino_linotype_bold.png | Bin 15512 -> 0 bytes captcha/fonts/perpetua_bold.png | Bin 12899 -> 0 bytes captcha/fonts/times_bold.png | Bin 13965 -> 0 bytes captcha/index.php | 10 - captcha/util/font_preparer.php | 42 ----- check_user.php | 1 + easylogin_accept.php | 1 + external_auth.php | 1 + home.php | 1 + index.php | 1 + libs/apmailer.php | 8 +- libs/captcha_utils.php | 38 +++- new_password.php | 1 + 18 files changed, 74 insertions(+), 313 deletions(-) delete mode 100644 captcha/captcha.php delete mode 100644 captcha/fonts/.htaccess delete mode 100644 captcha/fonts/palatino_linotype_bold.png delete mode 100644 captcha/fonts/perpetua_bold.png delete mode 100644 captcha/fonts/times_bold.png delete mode 100644 captcha/index.php delete mode 100644 captcha/util/font_preparer.php diff --git a/.configuration/example_config.php b/.configuration/example_config.php index 26b3965..2c268a0 100644 --- a/.configuration/example_config.php +++ b/.configuration/example_config.php @@ -57,6 +57,20 @@ function getScopes($expl_scopes, $infinite=0, $admin_required=false){ $spam_provider = "https://disposable.debounce.io/?email="; $captcha_required = true; + + /* + If using CAPTCHA (field $captcha_required is true), put the correct values + in the field below. For more info - https://dash.cloudflare.com/?to=/:account/turnstile + + $turnstile_public is your Site Key. + $turnstile_private is your Secret Key. + + If you aren't using CAPTCHA - set $captcha_required to false and ignore + the fields below. + */ + + $turnstile_public = ""; + $turnstile_private = ""; $login_site = "https://example.com/login"; $status_page = "https://status.example.com/"; diff --git a/api.php b/api.php index cb0f404..2c2a561 100644 --- a/api.php +++ b/api.php @@ -33,22 +33,29 @@ function isRateExceeded($method, $ip, $max_rate, $expired) { if (isset($_COOKIE['passed_captcha'])) { global $domain_name; global $captcha_required; + global $turnstile_private; if ($captcha_required) { - session_start(); - $captcha_true = $_SESSION['captcha_keystring']; - $captcha_time = $_SESSION['captcha_time']; - - $_SESSION['captcha_keystring'] = ""; - $_SESSION['captcha_time'] = ""; - setcookie("passed_captcha", "", time() - 3600, $domain_name); - - if ((strtolower($_COOKIE['passed_captcha']) == $captcha_true) && $captcha_true != '') { - if ($captcha_time + 180 >= time()) { - return false; - } + $turnstile_token = $_COOKIE['passed_captcha']; + + $ip = $_SERVER['REMOTE_ADDR']; + $url_path = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; + $data = array('secret' => $turnstile_private, 'response' => $turnstile_token, 'remoteip' => $ip); + + $options = array( + 'http' => array( + 'method' => 'POST', + 'content' => http_build_query($data)) + ); + + $stream = stream_context_create($options); + $result = file_get_contents($url_path, false, $stream); + + $responseKeys = json_decode($result,true); + + if($responseKeys['success']){ + return false; } - return true; } } return true; diff --git a/auth_manager.php b/auth_manager.php index 6f23782..32c71af 100644 --- a/auth_manager.php +++ b/auth_manager.php @@ -3,6 +3,7 @@ + diff --git a/captcha/captcha.php b/captcha/captcha.php deleted file mode 100644 index 1e7778e..0000000 --- a/captcha/captcha.php +++ /dev/null @@ -1,231 +0,0 @@ -keystring=''; - for($i=0;$i<$length;$i++){ - $this->keystring.=$allowed_symbols[random_int(0,strlen($allowed_symbols)-1)]; - } - if(!preg_match('/cp|cb|ck|c6|c9|rn|rm|mm|co|do|cl|db|qp|qb|dp|ww/', $this->keystring)) break; - } - - $font_file=$fonts[random_int(0, count($fonts)-1)]; - $font=imagecreatefrompng($font_file); - imagealphablending($font, true); - - $fontfile_width=imagesx($font); - $fontfile_height=imagesy($font)-1; - - $font_metrics=array(); - $symbol=0; - $reading_symbol=false; - - // loading font - for($i=0;$i<$fontfile_width && $symbol<$alphabet_length;$i++){ - $transparent = (imagecolorat($font, $i, 0) >> 24) == 127; - - if(!$reading_symbol && !$transparent){ - $font_metrics[$alphabet[$symbol]]=array('start'=>$i); - $reading_symbol=true; - continue; - } - - if($reading_symbol && $transparent){ - $font_metrics[$alphabet[$symbol]]['end']=$i; - $reading_symbol=false; - $symbol++; - continue; - } - } - - $img=imagecreatetruecolor($width, $height); - imagealphablending($img, true); - $white=imagecolorallocate($img, 255, 255, 255); - $black=imagecolorallocate($img, 0, 0, 0); - - imagefilledrectangle($img, 0, 0, $width-1, $height-1, $white); - - // draw text - $x=1; - $odd=random_int(0,1); - if($odd==0) $odd=-1; - for($i=0;$i<$length;$i++){ - $m=$font_metrics[$this->keystring[$i]]; - - $y=(($i%2)*$fluctuation_amplitude - $fluctuation_amplitude/2)*$odd - + random_int(-round($fluctuation_amplitude/3), round($fluctuation_amplitude/3)) - + ($height-$fontfile_height)/2; - - if($no_spaces){ - $shift=0; - if($i>0){ - $shift=10000; - for($sy=3;$sy<$fontfile_height-10;$sy+=1){ - for($sx=$m['start']-1;$sx<$m['end'];$sx+=1){ - $rgb=imagecolorat($font, $sx, $sy); - $opacity=$rgb>>24; - if($opacity<127){ - $left=$sx-$m['start']+$x; - $py=$sy+$y; - if($py>$height) break; - for($px=min($left,$width-1);$px>$left-200 && $px>=0;$px-=1){ - $color=imagecolorat($img, $px, $py) & 0xff; - if($color+$opacity<170){ // 170 - threshold - if($shift>$left-$px){ - $shift=$left-$px; - } - break; - } - } - break; - } - } - } - if($shift==10000){ - $shift=random_int(4,6); - } - - } - }else{ - $shift=1; - } - imagecopy($img, $font, $x-$shift, $y, $m['start'], 1, $m['end']-$m['start'], $fontfile_height); - $x+=$m['end']-$m['start']-$shift; - } - }while($x>=$width-10); // while not fit in canvas - - //noise - $white=imagecolorallocate($font, 255, 255, 255); - $black=imagecolorallocate($font, 0, 0, 0); - for($i=0;$i<(($height-30)*$x)*$white_noise_density;$i++){ - imagesetpixel($img, random_int(0, $x-1), random_int(10, $height-15), $white); - } - for($i=0;$i<(($height-30)*$x)*$black_noise_density;$i++){ - imagesetpixel($img, random_int(0, $x-1), random_int(10, $height-15), $black); - } - - - $center=$x/2; - - // credits. To remove, see configuration file - $img2=imagecreatetruecolor($width, $height+($show_credits?12:0)); - $foreground=imagecolorallocate($img2, $foreground_color[0], $foreground_color[1], $foreground_color[2]); - $background=imagecolorallocate($img2, $background_color[0], $background_color[1], $background_color[2]); - imagefilledrectangle($img2, 0, 0, $width-1, $height-1, $background); - imagefilledrectangle($img2, 0, $height, $width-1, $height+12, $foreground); - $credits=empty($credits)?$_SERVER['HTTP_HOST']:$credits; - imagestring($img2, 2, $width/2-imagefontwidth(2)*strlen($credits)/2, $height-2, $credits, $background); - - // periods - $rand1=random_int(750000,1200000)/10000000; - $rand2=random_int(750000,1200000)/10000000; - $rand3=random_int(750000,1200000)/10000000; - $rand4=random_int(750000,1200000)/10000000; - // phases - $rand5=random_int(0,31415926)/10000000; - $rand6=random_int(0,31415926)/10000000; - $rand7=random_int(0,31415926)/10000000; - $rand8=random_int(0,31415926)/10000000; - // amplitudes - $rand9=random_int(330,420)/110; - $rand10=random_int(330,450)/100; - - //wave distortion - - for($x=0;$x<$width;$x++){ - for($y=0;$y<$height;$y++){ - $sx=$x+(sin($x*$rand1+$rand5)+sin($y*$rand3+$rand6))*$rand9-$width/2+$center+1; - $sy=$y+(sin($x*$rand2+$rand7)+sin($y*$rand4+$rand8))*$rand10; - - if($sx<0 || $sy<0 || $sx>=$width-1 || $sy>=$height-1){ - continue; - }else{ - $color=imagecolorat($img, $sx, $sy) & 0xFF; - $color_x=imagecolorat($img, $sx+1, $sy) & 0xFF; - $color_y=imagecolorat($img, $sx, $sy+1) & 0xFF; - $color_xy=imagecolorat($img, $sx+1, $sy+1) & 0xFF; - } - - if($color==255 && $color_x==255 && $color_y==255 && $color_xy==255){ - continue; - }else if($color==0 && $color_x==0 && $color_y==0 && $color_xy==0){ - $newred=$foreground_color[0]; - $newgreen=$foreground_color[1]; - $newblue=$foreground_color[2]; - }else{ - $frsx=$sx-floor($sx); - $frsy=$sy-floor($sy); - $frsx1=1-$frsx; - $frsy1=1-$frsy; - - $newcolor=( - $color*$frsx1*$frsy1+ - $color_x*$frsx*$frsy1+ - $color_y*$frsx1*$frsy+ - $color_xy*$frsx*$frsy); - - if($newcolor>255) $newcolor=255; - $newcolor=$newcolor/255; - $newcolor0=1-$newcolor; - - $newred=$newcolor0*$foreground_color[0]+$newcolor*$background_color[0]; - $newgreen=$newcolor0*$foreground_color[1]+$newcolor*$background_color[1]; - $newblue=$newcolor0*$foreground_color[2]+$newcolor*$background_color[2]; - } - - imagesetpixel($img2, $x, $y, imagecolorallocate($img2, $newred, $newgreen, $newblue)); - } - } - - header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); - header('Cache-Control: no-store, no-cache, must-revalidate'); - header('Cache-Control: post-check=0, pre-check=0', FALSE); - header('Pragma: no-cache'); - if(function_exists("imagejpeg")){ - header("Content-Type: image/jpeg"); - imagejpeg($img2, null, $jpeg_quality); - }else if(function_exists("imagegif")){ - header("Content-Type: image/gif"); - imagegif($img2); - }else if(function_exists("imagepng")){ - header("Content-Type: image/x-png"); - imagepng($img2); - } - } - - function getKeyString(){ - return $this->keystring; - } -} - -?> \ No newline at end of file diff --git a/captcha/fonts/.htaccess b/captcha/fonts/.htaccess deleted file mode 100644 index 53b781b..0000000 --- a/captcha/fonts/.htaccess +++ /dev/null @@ -1,4 +0,0 @@ - - Order allow,deny - Deny from all - \ No newline at end of file diff --git a/captcha/fonts/palatino_linotype_bold.png b/captcha/fonts/palatino_linotype_bold.png deleted file mode 100644 index 76e653ad71afa37e9655c3f3934c5f1bb0a683dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15512 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%0A8lTo<^(6zt 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 diff --git a/captcha/fonts/perpetua_bold.png b/captcha/fonts/perpetua_bold.png deleted file mode 100644 index 861ea50ec92712f3168b5e1dfee67d932021e25d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12899 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 Vx~j>Po5Ldn&o%WmYSe5a{|}oi%54Au diff --git a/captcha/fonts/times_bold.png b/captcha/fonts/times_bold.png deleted file mode 100644 index 4048121076aa56d2840395ed5389bbf5b865fa45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13965 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 diff --git a/captcha/index.php b/captcha/index.php deleted file mode 100644 index a9132b8..0000000 --- a/captcha/index.php +++ /dev/null @@ -1,10 +0,0 @@ -getKeyString(); -$_SESSION['captcha_time'] = time(); -?> \ No newline at end of file diff --git a/captcha/util/font_preparer.php b/captcha/util/font_preparer.php deleted file mode 100644 index 1fb9344..0000000 --- a/captcha/util/font_preparer.php +++ /dev/null @@ -1,42 +0,0 @@ ->24; - if($opacity!=127){ - $space=false; - } - $column_opacity+=127-$opacity; - } - if(!$space){ - imageline($img,$x,0,$x,0,$column_opacity<200?$gray:$black); - } - } - imagepng($img,'../fonts/'.$file); - } - closedir($handle); -} -?> diff --git a/check_user.php b/check_user.php index 5b276e4..2335a58 100644 --- a/check_user.php +++ b/check_user.php @@ -4,6 +4,7 @@ + diff --git a/easylogin_accept.php b/easylogin_accept.php index 39df076..fefc4cd 100644 --- a/easylogin_accept.php +++ b/easylogin_accept.php @@ -4,6 +4,7 @@ + diff --git a/external_auth.php b/external_auth.php index e424e6d..6cc3347 100644 --- a/external_auth.php +++ b/external_auth.php @@ -4,6 +4,7 @@ + diff --git a/home.php b/home.php index d6f90f3..fed2daf 100644 --- a/home.php +++ b/home.php @@ -4,6 +4,7 @@ + diff --git a/index.php b/index.php index eca073d..3e5dae3 100644 --- a/index.php +++ b/index.php @@ -4,6 +4,7 @@ + diff --git a/libs/apmailer.php b/libs/apmailer.php index 74e91e5..44ae40d 100644 --- a/libs/apmailer.php +++ b/libs/apmailer.php @@ -1009,8 +1009,8 @@ public function encode($str) return mb_encode_mimeheader($str); } } - - class Part + + class Part { protected $headers = null; protected $content = null; @@ -1108,8 +1108,8 @@ private function encodeContent() return trim(chunk_split(base64_encode((string) $this->getContent()))); } } - - class MultiPart extends Part + + class MultiPart extends Part { private $boundary = null; diff --git a/libs/captcha_utils.php b/libs/captcha_utils.php index 81b1ad8..8483297 100644 --- a/libs/captcha_utils.php +++ b/libs/captcha_utils.php @@ -4,21 +4,41 @@ if($captcha_required){ $script = <<
', '', - function(evt, value){ - repeatRequest(value); - }, - function(){ - console.log("Cancelled CAPTCHA Event"); - } - ); - captcha_content.innerHTML = '
Введите символы с картинки ниже:


'; + if(window.cpi){ + alertify.notify("Проверка не была пройдена. Попробуйте позже!", 'error', 3); + window.cpi = false; + return; + } + window.alert_prompt = alertify.alert('CAPTCHA', 'Your request is being processed... Wait a bit.
'); + captcha_content.innerHTML = '
'; + turnstile.render('#turnstile_captcha', { + sitekey: '$turnstile_public', + callback: function(token) { + repeatRequest(token); + } + }); + + try{ + window.alert_prompt.close(); + } + catch(e){ + console.warn(e); + } } function repeatRequest(captcha){ + try{ + window.alert_prompt.close(); + } + catch(e){ + console.warn(e); + } + alertify.notify("Вы успешно прошли проверку!", 'success', 3); document.cookie = "passed_captcha=" + captcha + ";path=/"; window.failed_request(); + window.cpi = true; } EOT; } diff --git a/new_password.php b/new_password.php index ebba84f..4aa5640 100644 --- a/new_password.php +++ b/new_password.php @@ -4,6 +4,7 @@ + From b275aae7754938a191769ffee466d4d29759f8f4 Mon Sep 17 00:00:00 2001 From: DS Software Date: Tue, 17 Jan 2023 23:38:05 +0400 Subject: [PATCH 12/18] Changed the way of indicating about the check --- libs/captcha_utils.php | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/libs/captcha_utils.php b/libs/captcha_utils.php index 8483297..5037f14 100644 --- a/libs/captcha_utils.php +++ b/libs/captcha_utils.php @@ -16,26 +16,18 @@ function callCaptcha(){ turnstile.render('#turnstile_captcha', { sitekey: '$turnstile_public', callback: function(token) { + try{ + window.alert_prompt.close(); + } + catch(e){ + console.warn(e); + } repeatRequest(token); } }); - - try{ - window.alert_prompt.close(); - } - catch(e){ - console.warn(e); - } } function repeatRequest(captcha){ - try{ - window.alert_prompt.close(); - } - catch(e){ - console.warn(e); - } - alertify.notify("Вы успешно прошли проверку!", 'success', 3); document.cookie = "passed_captcha=" + captcha + ";path=/"; window.failed_request(); window.cpi = true; From 1d891af84da78e3d49f3e4de9394e8be92b6a0ae Mon Sep 17 00:00:00 2001 From: DS Software Date: Tue, 17 Jan 2023 23:40:18 +0400 Subject: [PATCH 13/18] Update example_config.php --- .configuration/example_config.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.configuration/example_config.php b/.configuration/example_config.php index 2c268a0..f7cfab5 100644 --- a/.configuration/example_config.php +++ b/.configuration/example_config.php @@ -56,8 +56,6 @@ function getScopes($expl_scopes, $infinite=0, $admin_required=false){ $spam_check = true; $spam_provider = "https://disposable.debounce.io/?email="; - $captcha_required = true; - /* If using CAPTCHA (field $captcha_required is true), put the correct values in the field below. For more info - https://dash.cloudflare.com/?to=/:account/turnstile @@ -69,6 +67,8 @@ function getScopes($expl_scopes, $infinite=0, $admin_required=false){ the fields below. */ + $captcha_required = true; + $turnstile_public = ""; $turnstile_private = ""; From 853a881ad81dfb9d32efd28e00bc7acd33fc8940 Mon Sep 17 00:00:00 2001 From: ArtemDev Date: Wed, 18 Jan 2023 00:08:52 +0400 Subject: [PATCH 14/18] Rewrote README --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d7d6ddf..114bb52 100644 --- a/README.md +++ b/README.md @@ -24,15 +24,58 @@ The only file you need to modify is config.php. * $maintenance_mode * If `true`, API returns only error MAINTENANCE_MODE to all the requests. * Used, if you want to stop users from using ULS while maintaining it. + +* $spam_check + * If `true`, users won't be able to use disposal emails. +* $spam_provide + * Sets the provider to check emails whether they are disposable or not. + +* $captcha_required + * If `true`, API will count user requests and issue proper RATE_LIMIT_EXCEEDED errors. +* $turnstile_public + * Required in order to work with CloudFlare Turnstile. Your Site Key. +* $turnstile_private + * Required in order to work with CloudFlare Turnstile. Your Secret Key. + +* $login_site + * URL of main page. +* $status_page + * URL of status page. +* $support + * Support e-mail or link + * $domain_name * Used to create cookies with proper path. * If you don't use root folder of your site, put the extension here: * If you use something like `https://dssoftware.ru/login` - put `/login` here * If you use something like `https://dssoftware.ru/` - leave `/` * If you use something like `https://example.dssoftware.ru/login` - put `/login` here + * $session_length * Defines the length of a random_session_id. If too small, RSID will duplicate. If too big, might cause some performance issues. * Optimal value - `32` + +* $service_key + * Used to verify data obtained from external sources - use a moderately long one. +* $encryption_key + * Used to encrypt data that cannot be stored for some reason. + +* $database + * Fill that array with data obtained from your database provider. + +* $email_info + * Fill that array with data you want to be shown in GUI. + +* $email_settings + * Fill that array with data obtained from your email host provider. + +* $enable_creation + * If `true`, API will allow to create projects, doesn't affect admins. +* $integrations_limit + * Sets the maximum amount of integrations that could be created. Doesn't affect admins. +* $allowed_admins + * Gives administrative permissions to specific users. + * WARNING! Do not give admin permissions to accounts without a reason. ### Database Configuration There is a Database Dump inside a .configuration folder. Use database_setup.sql as an Import File in PHPMyAdmin or just execute the SQL commands inside the file. @@ -48,9 +91,8 @@ There is a list of all libraries that are used in ULS. * PHP TOTP (https://github.com/lfkeitel/php-totp/) * Html5-QRCode (https://github.com/mebjas/html5-qrcode) * Alertify JS (https://github.com/MohammadYounes/AlertifyJS) -* KCaptcha (http://www.captcha.ru/kcaptcha/) ## Contributing & Issues When contributing changes to the project, please provide as much detail on the changes. Malicious or meaningless contributions won't be accepted. Please, if you found an issue in ULS, create a GitHub issue. -Besides, you can contact DS Software team: https://dssoftware.ru/about/ \ No newline at end of file +Besides, you can contact DS Software team: https://dssoftware.ru/about/ From 28a5b5df81b05bccd29852d552899351839a3881 Mon Sep 17 00:00:00 2001 From: DS Software Date: Sun, 3 Sep 2023 13:17:52 +0400 Subject: [PATCH 15/18] Tons of fixes 1) Fixed disposable mail bug 2) Added email disable flag 3) Added Webauthn support 4) Removed unused libs --- api.php | 409 ++++++------ database.php | 157 +++-- home.php | 353 ++++++---- index.php | 497 +++++++------- libs/email_handler.php | 12 +- libs/gen_2fa_qr.php | 6 - libs/qr_reader.min.js | 8 - .../Attestation/AttestationObject.php | 179 +++++ .../Attestation/AuthenticatorData.php | 423 ++++++++++++ .../Attestation/Format/AndroidKey.php | 96 +++ .../Attestation/Format/AndroidSafetyNet.php | 152 +++++ libs/webauthn/Attestation/Format/Apple.php | 139 ++++ .../Attestation/Format/FormatBase.php | 193 ++++++ libs/webauthn/Attestation/Format/None.php | 41 ++ libs/webauthn/Attestation/Format/Packed.php | 139 ++++ libs/webauthn/Attestation/Format/Tpm.php | 180 +++++ libs/webauthn/Attestation/Format/U2f.php | 93 +++ libs/webauthn/Binary/ByteBuffer.php | 300 +++++++++ libs/webauthn/CBOR/CborDecoder.php | 220 ++++++ libs/webauthn/WebAuthn.php | 626 ++++++++++++++++++ libs/webauthn/WebAuthnException.php | 28 + 21 files changed, 3560 insertions(+), 691 deletions(-) delete mode 100644 libs/qr_reader.min.js create mode 100644 libs/webauthn/Attestation/AttestationObject.php create mode 100644 libs/webauthn/Attestation/AuthenticatorData.php create mode 100644 libs/webauthn/Attestation/Format/AndroidKey.php create mode 100644 libs/webauthn/Attestation/Format/AndroidSafetyNet.php create mode 100644 libs/webauthn/Attestation/Format/Apple.php create mode 100644 libs/webauthn/Attestation/Format/FormatBase.php create mode 100644 libs/webauthn/Attestation/Format/None.php create mode 100644 libs/webauthn/Attestation/Format/Packed.php create mode 100644 libs/webauthn/Attestation/Format/Tpm.php create mode 100644 libs/webauthn/Attestation/Format/U2f.php create mode 100644 libs/webauthn/Binary/ByteBuffer.php create mode 100644 libs/webauthn/CBOR/CborDecoder.php create mode 100644 libs/webauthn/WebAuthn.php create mode 100644 libs/webauthn/WebAuthnException.php diff --git a/api.php b/api.php index 2c2a561..d7e338a 100644 --- a/api.php +++ b/api.php @@ -1,6 +1,10 @@ countSessionsByIP($_SERVER['REMOTE_ADDR']) >= 10) { - $login_db->deleteSessionsByIP($_SERVER['REMOTE_ADDR']); - returnError("RATE_LIMIT_FOR_THIS_IP"); - } - - $session = "session_" . convBase(uniqidReal(256), 16, 36); - $sess_salt = convBase(uniqidReal(32), 16, 36); - $login_db->createELSession($session, $sess_salt, $_SERVER['REMOTE_ADDR']); - - $session_ver = hash("sha256", $session . "_" . $service_key . "_" . $sess_salt . "_" . $_SERVER['REMOTE_ADDR']); - - $browser = getBrowser(); - - $ua = array( - 'browser' => $browser['name'], - 'version' => $browser['version'], - 'platform' => ucfirst($browser['platform']), - 'ip' => $_SERVER['REMOTE_ADDR'] - ); - - $ua = json_encode($ua); - - $session_link = base64_encode($login_site . "/easylogin_accept.php?session_id=" . $session . "&session_ver=" . $session_ver . "&user_agent=" . urlencode(base64_encode($ua)) . "&user_agent_ver=" . hash("sha256", $ua . "_" . $service_key)); - - $session_qr = "libs/gen_2fa_qr.php?method=EasyLoginSession&session=" . $session_link; - - $return = array( - 'result' => "OK", - 'session' => $session, - 'session_qr' => $session_qr, - 'session_verifier' => $session_ver - ); - - echo json_encode($return); - die(); - } - - if ($method == "removeELSession") { - if (isRateExceeded($section . '-' . $method, $_SERVER['REMOTE_ADDR'], 10, 60)) { - returnError("RATE_LIMIT_EXCEEDED"); - } - - $session = $login_db->getELSession($_REQUEST['session_id']); + if ($method == "getWebauthnSession") { + try{ + $WebAuthn = new lbuchs\WebAuthn\WebAuthn($platform_name, $relying_party_id, null); + $getArgs = $WebAuthn->getGetArgs([], 300, true, true, true, true, true, $user_verification_requirement === "required"); - if ($session['session'] != '') { - $true_sess_ver = hash("sha256", $session['session'] . "_" . $service_key . "_" . $session['session_seed'] . "_" . $_SERVER['REMOTE_ADDR']); + $_SESSION['challenge'] = $WebAuthn->getChallenge(); - if ($true_sess_ver == $_REQUEST['session_ver']) { - $login_db->deleteELSession($session['session']); - $return = array( - 'result' => "OK", - 'description' => "Success" - ); + $return = array( + 'result' => 'OK', + 'description' => 'WAITING_INTERACTION', + 'args' => $getArgs + ); - echo json_encode($return); - } - else{ - returnError("UNAUTHORIZED"); - } + echo json_encode($return); + die(); } - else{ - returnError("WRONG_SESSION"); + catch(Exception $e){ + returnError("FAILED_TO_AUTHENTICATE"); } - die(); } - if ($method == "checkELSession") { - $session = $login_db->getELSession($_REQUEST['session_id']); + if ($method == "verifyWebauthnSession") { + try{ + $WebAuthn = new lbuchs\WebAuthn\WebAuthn($platform_name, $relying_party_id, null); + $post = trim(file_get_contents('php://input')); + if ($post) { + $post = json_decode($post); + } + + $clientDataJSON = base64_decode($post->clientDataJSON); + $authenticatorData = base64_decode($post->authenticatorData); + $signature = base64_decode($post->signature); + $userHandle = base64_decode($post->userHandle); + $id = base64_decode($post->id); + $challenge = $_SESSION['challenge'] ?? ''; + $credentialPublicKey = null; - if ($session['session'] == '') { - returnError("WRONG_SESSION"); - } + $passkey_info = $login_db->getPKByCredentials(base64_encode($id)); + if($passkey_info === false){ + returnError("FAILED_TO_AUTHENTICATE"); + } - if ($session['created'] + 300 < time()) { - $login_db->deleteELSession($session['session']); - returnError("WRONG_SESSION"); - } + $final_key = json_decode($passkey_info['final_key']); - $true_sess_ver = hash("sha256", $session['session'] . "_" . $service_key . "_" . $session['session_seed'] . "_" . $_SERVER['REMOTE_ADDR']); + $credentialPublicKey = $final_key->credentialPublicKey; - if ($true_sess_ver != $_REQUEST['session_ver']) { - returnError("UNAUTHORIZED"); - } + if ($credentialPublicKey === null) { + returnError("FAILED_TO_AUTHENTICATE"); + } - if ($session['claimed'] != 1) { - returnError("UNCLAIMED"); - } + if ($userHandle !== hex2bin($final_key->userId) && $userHandle != null) { + returnError("FAILED_TO_AUTHENTICATE"); + } - $user_info = $login_db->getUserInfo($session['user_id']); + $WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, null, $user_verification_requirement === "required"); - if ($user_info['easylogin'] != 1) { - returnError("THIS_FEATURE_WAS_DISABLED_BY_OWNER"); - } + $user_info = $login_db->getUserInfo($passkey_info['owner_id']); + if($user_info['user_id'] != $passkey_info['owner_id']){ + returnError("FAILED_TO_AUTHENTICATE"); + } - if ($_SERVER['REMOTE_ADDR'] != $session['ip']) { - returnError("UNKNOWN_IP"); - } + $rsid = bin2hex(random_bytes($session_length / 2)); + $timestamp = time(); - $rsid = bin2hex(random_bytes($session_length / 2)); - $timestamp = time(); + $login_db->setUserIP($user_info['user_id'], $_SERVER['REMOTE_ADDR']); - $login_db->setUserIP($user_info['user_id'], $_SERVER['REMOTE_ADDR']); - $login_db->deleteELSession($session['session']); + setcookie("user_id", $user_info['user_id'], time() + 2678400, $domain_name); + setcookie("email", $user_info['user_email'], time() + 2678400, $domain_name); + setcookie("user_ip", $_SERVER['REMOTE_ADDR'], time() + 2678400, $domain_name); + setcookie("user_verkey", hash("sha512", "{$rsid}_{$user_info['user_email']}_{$user_info['user_id']}_{$user_info['SLID']}_{$_SERVER['REMOTE_ADDR']}_$service_key"), time() + 2678400, $domain_name); + setcookie("session", $rsid, time() + 2678400, $domain_name); + setcookie("SLID", $user_info['SLID'], time() + 2678400, $domain_name); - setcookie("user_id", $user_info['user_id'], time() + 2678400, $domain_name); - setcookie("email", $user_info['user_email'], time() + 2678400, $domain_name); - setcookie("user_ip", $_SERVER['REMOTE_ADDR'], time() + 2678400, $domain_name); - setcookie("user_verkey", hash("sha512", "{$rsid}_{$user_info['user_email']}_{$user_info['user_id']}_{$user_info['SLID']}_{$_SERVER['REMOTE_ADDR']}_$service_key"), time() + 2678400, $domain_name); - setcookie("session", $rsid, time() + 2678400, $domain_name); - setcookie("SLID", $user_info['SLID'], time() + 2678400, $domain_name); + $ip_verify = hash("sha512", "{$user_info['SLID']}_{$user_info['user_id']}_{$_SERVER['REMOTE_ADDR']}_$service_key"); + setcookie("ip_verify", $ip_verify, time() + 2678400, $domain_name); - $ip_verify = hash("sha512", "{$user_info['SLID']}_{$user_info['user_id']}_{$_SERVER['REMOTE_ADDR']}_$service_key"); - setcookie("ip_verify", $ip_verify, time() + 2678400, $domain_name); + if ($user_info['2fa_active'] == 1) { + $totp_timestamp = time(); + $true_totp_ver = hash("sha512", "{$user_info['SLID']}_{$user_info['2fa_secret']}_{$user_info['user_id']}_{$totp_timestamp}_$service_key"); - $return = array( - 'result' => 'OK', - 'description' => 'Success' - ); + setcookie("totp_timestamp", $totp_timestamp, time() + 2678400, $domain_name); + setcookie("totp_verification", $true_totp_ver, time() + 2678400, $domain_name); + } - echo json_encode($return); - die(); + $return = array( + 'result' => "OK", + 'desc' => "AUTHENTICATED" + ); + echo json_encode($return); + die(); + } + catch(Exception $e){ + returnError("FAILED_TO_AUTHENTICATE"); + } } } else{ @@ -1338,8 +1309,7 @@ function uniqidReal($length = 16) { $return = array( 'result' => "OK", 'totp' => $uinfo['2fa_active'], - 'email_check' => $uinfo['email_check'], - 'easylogin' => $uinfo['easylogin'] + 'email_check' => $uinfo['email_check'] ); echo json_encode($return); @@ -1455,95 +1425,7 @@ function uniqidReal($length = 16) { } } - if ($section == "easylogin" && $token_scopes['profile_management']) { - if ($method == "enable") { - if ($uinfo['easylogin'] == 0) { - $login_db->setEasyloginState($user_id, 1); - - $return = array( - 'result' => "OK", - 'description' => 'Success' - ); - echo json_encode($return); - die(); - } - else{ - returnError("EASYLOGIN_WAS_ENABLED_BEFORE"); - } - } - - if ($method == "disable") { - if ($uinfo['easylogin'] == 1) { - $login_db->setEasyloginState($user_id, 0); - - $return = array( - 'result' => "OK", - 'description' => 'Success' - ); - echo json_encode($return); - die(); - } - else{ - returnError("EASYLOGIN_WAS_DISABLED_BEFORE"); - } - } - - if ($method == "claim") { - $session = $login_db->getELSession($_REQUEST['session_id']); - - if ($session['session'] == '') { - returnError("WRONG_SESSION"); - } - - $true_sess_ver = hash("sha256", $session['session'] . "_" . $service_key . "_" . $session['session_seed'] . "_" . $session['ip']); - if ($true_sess_ver != $_REQUEST['session_ver']) { - returnError("UNAUTHORIZED"); - } - if ($session['created'] + 300 < time()) { - $login_db->deleteELSession($session['session']); - returnError("TIMEOUT"); - } - - if ($uinfo['easylogin'] != 1) { - returnError("THIS_FEATURE_WAS_DISABLED_BY_OWNER"); - } - if ($uinfo['2fa_active'] != 1) { - returnError("2FA_DISABLED"); - } - - $login_db->claimELSession($user_id, $session['session']); - - $return = array( - 'result' => "OK", - 'description' => 'Success' - ); - echo json_encode($return); - die(); - } - - if ($method == "checkELInfo") { - $useragent_encoded = $_GET['user_agent']; - $useragent_ver = $_GET['user_agent_ver']; - - $true_ver = hash("sha256", base64_decode($useragent_encoded) . "_" . $service_key); - - if($true_ver == $useragent_ver){ - $user_agent = json_decode(base64_decode($useragent_encoded), true); - - $return = array( - 'result' => "OK", - 'browser' => $user_agent['browser'], - 'version' => $user_agent['version'], - 'platform' => $user_agent['platform'], - 'ip' => $user_agent['ip'] - ); - echo json_encode($return); - die(); - } - returnError("INVALID_USER_AGENT"); - } - } if ($section == "integration" && $token_scopes['profile_management']) { if ($method == "getUserProjects") { @@ -1711,13 +1593,11 @@ function uniqidReal($length = 16) { if ($section == "admin" && $token_scopes['admin'] && $allowed_admins[$user_id]) { if ($method == "getStats") { $request_st = $login_db->getRequestStats(); - $session_st = $login_db->getSessionStats(); $user_st = $login_db->getUserStats(); $project_st = $login_db->getProjectStats(); $return = array( 'result' => "OK", 'requests' => $request_st, - 'sessions' => $session_st, 'users' => $user_st, 'projects' => $project_st ); @@ -1735,16 +1615,6 @@ function uniqidReal($length = 16) { die(); } - if ($method == "purgeSessions") { - $login_db->cleanupSessions(); - $return = array( - 'result' => "OK", - 'description' => 'Success' - ); - echo json_encode($return); - die(); - } - if ($method == "getProjectInfo") { $project = $login_db->getAdminProjectInfo($_REQUEST['project_id']); if (!$project["exists"]) { @@ -1851,7 +1721,6 @@ function uniqidReal($length = 16) { 'user_name' => $user['user_name'], 'user_surname' => $user['user_surname'], 'verified' => $user['verified'], - 'easylogin' => $user['easylogin'], 'email_check' => $user['email_check'], '2fa_active' => $user['2fa_active'], 'admin' => $is_admin, @@ -2016,6 +1885,122 @@ function uniqidReal($length = 16) { die(); } } + + if ($section == "webauthn") { + try{ + if ($method === 'getPasskeys') { + $passkeys_list = $login_db->getPasskeys($uinfo['user_id']); + + + $return = array( + 'result' => "OK", + 'keys' => $passkeys_list, + 'attest_format' => $attestation_formats + ); + echo json_encode($return); + die(); + } + + if ($method === 'removePasskey') { + $pkuid = $_GET['PK_UID']; + + $login_db->removePasskey($uinfo['user_id'], $pkuid); + + $return = array( + 'result' => "OK" + ); + echo json_encode($return); + die(); + } + + + if ($method === 'getNewKeyArgs') { + $WebAuthn = new lbuchs\WebAuthn\WebAuthn($platform_name, $relying_party_id, null); + + $userId = bin2hex(random_bytes(64)); + $userName = $uinfo['user_email']; + $userDisplayName = $uinfo['user_nick']; + + $userId = preg_replace('/[^0-9a-f]/i', '', $userId); + /*$userName = preg_replace('/[^0-9a-z]/i', '_', $userName); + $userDisplayName = preg_replace('/[^0-9a-z öüäéèàÖÜÄÉÈÀÂÊÎÔÛâêîôû]/i', '_', $userDisplayName);*/ + + $createArgs = $WebAuthn->getCreateArgs(hex2bin($userId), $userName, $userDisplayName, 300, true, $user_verification_requirement, null); + + $return = array( + 'result' => "OK", + 'description' => "WAITING_VERIFICATION", + 'args' => $createArgs + ); + echo json_encode($return); + + $_SESSION['challenge'] = $WebAuthn->getChallenge(); + $_SESSION['pkid'] = $userId; + + die(); + } + + $post = trim(file_get_contents('php://input')); + if ($post) { + $post = json_decode($post); + } + + if ($method === 'createNewKey') { + $WebAuthn = new lbuchs\WebAuthn\WebAuthn($platform_name, $relying_party_id, null); + + $userName = $uinfo['user_nick']; + $userDisplayName = $uinfo['user_email']; + + /*$userName = preg_replace('/[^0-9a-z]/i', '_', $userName); + $userDisplayName = preg_replace('/[^0-9a-z öüäéèàÖÜÄÉÈÀÂÊÎÔÛâêîôû]/i', '_', $userDisplayName);*/ + + $clientDataJSON = base64_decode($post->clientDataJSON); + $attestationObject = base64_decode($post->attestationObject); + $challenge = $_SESSION['challenge']; + + $data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $challenge, $user_verification_requirement === "required", true, false); + $userId = $_SESSION['pkid']; + + $data->userId = $userId; + $data->userName = $userName; + $data->userDisplayName = $userDisplayName; + + $credential_id = base64_encode($data->credentialId); + + $passkey_serialized = array( + "rpId" => $data->rpId, + "attestationFormat" => $data->attestationFormat, + "credentialId" => base64_encode($data->credentialId), + "credentialPublicKey" => $data->credentialPublicKey, + "certificateChain" => $data->certificateChain, + "certificate" => $data->certificate, + "certificateIssuer" => $data->certificateIssuer, + "certificateSubject" => $data->certificateSubject, + "signatureCounter" => $data->signatureCounter, + "AAGUID" => base64_encode($data->AAGUID), + "rootValid" => $data->rootValid, + "userPresent" => $data->userPresent, + "userVerified" => $data->userVerified, + "userId" => $data->userId, + "userName" => $data->userName, + "userDisplayName" => $data->userDisplayName, + ); + + $login_db->savePasskey($uinfo['user_id'], $userId, json_encode($passkey_serialized), $credential_id, $data->attestationFormat); + + $return = array( + 'result' => "OK", + 'description' => "SUCCESS" + ); + echo json_encode($return); + die(); + } + } + catch(Exception $e){ + echo($e); + returnError("FAILED_TO_AUTHENTICATE"); + } + } } returnError("UNKNOWN_METHOD_OR_SECTION"); diff --git a/database.php b/database.php index 84c3297..2617011 100644 --- a/database.php +++ b/database.php @@ -19,11 +19,11 @@ public function __construct($database){ public function getUserInfo($user_id){ $login_db = $this->ldb; $user_id = $login_db->real_escape_string($user_id); - $req = "SELECT `user_id`, `user_nick`, `user_email`, `user_name`, `user_surname`, `birthday`, `verified`, `user_salt`, `password_hash`, `ip_ver_code`, `user_ip`, `api_key_seed`, `SLID`, `last_sid`, `easylogin`, `email_check`, `2fa_active`, `2fa_secret`, `2fa_disable_code`, `is_banned`, `ban_reason` FROM `users` WHERE `user_id`='$user_id'"; + $req = "SELECT `user_id`, `user_nick`, `user_email`, `user_name`, `user_surname`, `birthday`, `verified`, `user_salt`, `password_hash`, `ip_ver_code`, `user_ip`, `api_key_seed`, `SLID`, `last_sid`, `email_check`, `2fa_active`, `2fa_secret`, `2fa_disable_code`, `is_banned`, `ban_reason` FROM `users` WHERE `user_id`='$user_id'"; $user_id = null; $statement = $login_db->prepare($req); $statement->execute(); - $statement->bind_result($user_id, $user_nick, $user_email, $user_name, $user_surname, $birthday, $verified, $salt, $password_hash, $ip_ver_code, $user_ip, $api_key_seed, $SLID, $last_sid, $easylogin, $email_check, $totp_active, $totp_secret, $totp_disable_code, $is_banned, $ban_reason); + $statement->bind_result($user_id, $user_nick, $user_email, $user_name, $user_surname, $birthday, $verified, $salt, $password_hash, $ip_ver_code, $user_ip, $api_key_seed, $SLID, $last_sid, $email_check, $totp_active, $totp_secret, $totp_disable_code, $is_banned, $ban_reason); $statement->fetch(); $user_object = array( @@ -41,7 +41,6 @@ public function getUserInfo($user_id){ 'api_key_seed' => $api_key_seed, 'SLID' => $SLID, 'last_sid' => $last_sid, - 'easylogin' => $easylogin, 'email_check' => $email_check, '2fa_active' => $totp_active, '2fa_secret' => $totp_secret, @@ -104,14 +103,6 @@ public function setTOTPState($user_id, $state){ $req = "UPDATE `users` SET `2fa_active`='$state' WHERE `user_id`='$user_id'"; $login_db->query($req); } - - public function setEasyloginState($user_id, $state){ - $login_db = $this->ldb; - $user_id = $login_db->real_escape_string($user_id); - $state = $login_db->real_escape_string($state); - $req = "UPDATE `users` SET `easylogin`='$state' WHERE `user_id`='$user_id'"; - $login_db->query($req); - } public function setTOTPSecret($user_id, $totp_secret){ $login_db = $this->ldb; @@ -183,52 +174,6 @@ public function wasEmailRegistered($user_email){ return false; } - public function createELSession($session_key, $session_salt, $ip){ - $login_db = $this->ldb; - $session_key = $login_db->real_escape_string($session_key); - $session_salt = $login_db->real_escape_string($session_salt); - $ip = $login_db->real_escape_string($ip); - $created = time(); - $req = "INSERT INTO `sessions`(`session`, `session_seed`, `claimed`, `created`, `session_ip`) VALUES ('$session_key', '$session_salt', 0, $created, '$ip')"; - $login_db->query($req); - return $login_db->insert_id; - } - - public function getELSession($session_key){ - $login_db = $this->ldb; - $session_key = $login_db->real_escape_string($session_key); - $req = "SELECT `session`, `session_seed`, `user_id`, `claimed`, `created`, `session_ip` FROM `sessions` WHERE `session`='$session_key'"; - $statement = $login_db->prepare($req); - $statement->execute(); - $statement->bind_result($session, $session_seed, $user_id, $claimed, $created, $ip); - $statement->fetch(); - $result = array( - 'session' => $session, - 'session_seed' => $session_seed, - 'user_id' => $user_id, - 'claimed' => $claimed, - 'created' => $created, - 'ip' => $ip - ); - - return $result; - } - - public function claimELSession($user_id, $session_key){ - $login_db = $this->ldb; - $user_id = $login_db->real_escape_string($user_id); - $session_key = $login_db->real_escape_string($session_key); - $req = "UPDATE `sessions` SET `claimed`=1, `user_id`='$user_id' WHERE `session`='$session_key' AND `claimed`=0"; - $login_db->query($req); - } - - public function deleteELSession($session_key){ - $login_db = $this->ldb; - $session_key = $login_db->real_escape_string($session_key); - $req = "DELETE FROM `sessions` WHERE `session`='$session_key'"; - $login_db->query($req); - } - public function deleteProject($project_id){ $login_db = $this->ldb; $project_id = $login_db->real_escape_string($project_id); @@ -471,7 +416,7 @@ public function getRequests($method, $user_ip){ $method = $login_db->real_escape_string($method); $user_ip = $login_db->real_escape_string($user_ip); - $req = "SELECT COUNT(*), `request_time` FROM `requests` WHERE `request_ip`='$user_ip' AND `method`='$method' LIMIT 1"; + $req = "SELECT COUNT(*), `request_time` FROM `requests` WHERE `request_ip`='$user_ip' AND `method`='$method' GROUP BY `request_time` LIMIT 1"; $statement = $login_db->prepare($req); $statement->execute(); @@ -552,18 +497,6 @@ public function getRequestStats(){ return $count; } - public function getSessionStats(){ - $login_db = $this->ldb; - - $req = "SELECT COUNT(*) FROM `sessions`"; - - $statement = $login_db->prepare($req); - $statement->execute(); - $statement->bind_result($count); - $statement->fetch(); - return $count; - } - public function getUserStats(){ $login_db = $this->ldb; @@ -593,10 +526,88 @@ public function cleanupRequests(){ $req = "TRUNCATE `requests`"; $login_db->query($req); } - - public function cleanupSessions(){ + + /* Webauthn Methods */ + public function getPKByCredentials($credential_id){ $login_db = $this->ldb; - $req = "TRUNCATE `sessions`"; + + $credential_id = $login_db->real_escape_string($credential_id); + + $req = "SELECT `user_id`, `owner_id`, `final_key` FROM `webauthn` WHERE `credential_id`='$credential_id'"; + + $statement = $login_db->prepare($req); + $statement->execute(); + $statement->bind_result($user_id, $owner_id, $final_key); + $response = false; + if($statement->fetch()){ + $response = [ + "final_key" => $final_key, + "owner_id" => $owner_id, + "user_id" => $user_id + ]; + } + return $response; + } + + public function savePasskey($owner_id, $user_id, $final_key, $credential_id, $attest_format = "none"){ + $login_db = $this->ldb; + + $owner_id = $login_db->real_escape_string($owner_id); + $user_id = $login_db->real_escape_string($user_id); + $final_key = $login_db->real_escape_string($final_key); + $credential_id = $login_db->real_escape_string($credential_id); + $attest_format = $login_db->real_escape_string($attest_format); + + $req = "INSERT INTO `webauthn`(`user_id`, `owner_id`, `final_key`, `credential_id`, `attest_type`) VALUES ('$user_id','$owner_id','$final_key','$credential_id', '$attest_format')"; + $login_db->query($req); + } + + public function getPasskeys($owner_id){ + $login_db = $this->ldb; + + $owner_id = $login_db->real_escape_string($owner_id); + + $req = "SELECT `keyID`, `attest_type` FROM `webauthn` WHERE `owner_id`='$owner_id'"; + + $statement = $login_db->prepare($req); + $statement->execute(); + $statement->bind_result($keyID, $attest_type); + $response = []; + while($statement->fetch()){ + $response[] = array( + 'key_id' => $keyID, + 'attest_type' => $attest_type + ); + } + + return $response; + } + + public function getCredentialIDS($owner_id){ + $login_db = $this->ldb; + + $owner_id = $login_db->real_escape_string($owner_id); + + $req = "SELECT `credential_id` FROM `webauthn` WHERE `owner_id`='$owner_id'"; + + $statement = $login_db->prepare($req); + $statement->execute(); + $statement->bind_result($credential_id); + $response = []; + while($statement->fetch()){ + $response[] = base64_decode($credential_id); + } + + return $response; + } + + public function removePasskey($owner_id, $key_id){ + $login_db = $this->ldb; + + $owner_id = $login_db->real_escape_string($owner_id); + $key_id = $login_db->real_escape_string($key_id); + + $req = "DELETE FROM `webauthn` WHERE `owner_id`='$owner_id' AND `keyID`='$key_id'"; $login_db->query($req); } } diff --git a/home.php b/home.php index fed2daf..36a444f 100644 --- a/home.php +++ b/home.php @@ -3,11 +3,11 @@ + - - - + + API - -
-
-
-

Беспарольный Вход

-
-
-
- -
-
+
+

Вход при помощи Ключа Доступа

+
+ + + Создать ключ +     +

+ Вы сможете войти при помощи этого ключа. +
+

Список ключей:

+
+

@@ -215,13 +211,6 @@ function close_sidebar(){
Количество запросов к методам, ограниченным лимитами.

- - - Количество Сессий EasyLogin: -     -
- Количество активных сессий EasyLogin созданных для беспарольного входа. -

Количество Пользователей: @@ -246,13 +235,6 @@ function close_sidebar(){

Очистит таблицу запросов, что приведёт к обнулению лимитов API.

- - - Очистить сессии EasyLogin -     -

- Очистит таблицу сессий EasyLogin, что приведёт к аннулированию предыдущих сессий. -

Управление Проектом @@ -335,7 +317,6 @@ function close_sidebar(){

Функции Безопасности

  -  

@@ -574,7 +555,7 @@ function checkAPIToken(){ || tab == "personal_info" || tab == "security" || tab == "api" - || tab == "easylogin"){ + || tab == "webauthn"){ choose_tab(tab); if(!(tab == "main" || tab == "personal_info")){ load_admin(); @@ -606,14 +587,6 @@ function choose_tab(tab){ if(window.current_tab == tab){ return; } - if(window.current_tab == "easylogin"){ - try{ - window.html5QrCode.stop(); - } - catch(err){ - console.log(err); - } - } window.current_tab = tab; @@ -638,9 +611,9 @@ function choose_tab(tab){ sel_tab.innerHTML = api.textContent; update_state = true; } - if(tab == "easylogin"){ - sel_tab.innerHTML = easylogin.textContent; - initQRReader(); + if(tab == "webauthn"){ + sel_tab.innerHTML = webauthn.textContent; + prepareWebauthn(); update_state = true; } if(tab == "admin" && window.is_admin){ @@ -696,6 +669,164 @@ function choose_tab(tab){ Internal Functions */ +function deleteWebauthnKey(key_id){ + alertify.confirm('Удаление Ключа', 'Ключ будет безвозвратно удалён и вы больше не сможете входить с помощью этого ключа! Вы уверены, что хотите продолжить?', function(){ + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'api.php?section=webauthn&method=removePasskey&PK_UID=' + key_id, true); + xhr.setRequestHeader("Authorization", "Bearer " + window.token); + xhr.send(); + xhr.onload = function (e) { + if (xhr.readyState == 4 && xhr.status == 200) { + let result = JSON.parse(xhr.responseText); + if(result.result == "OK"){ + prepareWebauthn(); + alertify.notify("Ключ был успешно удалён.", 'success', 5); + } + else{ + alertify.notify("Произошла ошибка при удалении ключа.", 'error', 5); + } + } + }; + }, function(){}); +} + +function prepareWebauthn(){ + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'api.php?section=webauthn&method=getPasskeys', true); + xhr.setRequestHeader("Authorization", "Bearer " + window.token); + xhr.send(); + xhr.onload = function (e) { + if (xhr.readyState == 4 && xhr.status == 200) { + let result = JSON.parse(xhr.responseText); + if(result.result == "OK"){ + webauthn_keys.innerHTML = ""; + + function format_key_data(key_data){ + let key_div = document.createElement('span'); + let key_icon = document.createElement('span'); + let key_description = document.createElement('span'); + + let key_type = result.attest_format?.[key_data.attest_type]; + + key_icon.classList.add("fa-solid"); + key_icon.classList.add(key_type?.icon == undefined ? "fa-key" : key_type?.icon); + key_icon.classList.add("content-icon"); + + key_description.textContent = key_type?.name == undefined ? "Passkey" : key_type?.name; + + key_div.append(key_icon, key_description); // Important Div + + let cancel_button = ``; // Important Div + + let revoke_desc = document.createElement('span'); // Important Div + revoke_desc.innerHTML = "Если отозвать ключ, то вы больше не сможете
авторизоваться при помощи этого ключа."; + revoke_desc.classList.add("hint-text"); + + webauthn_keys.append(key_div); + webauthn_keys.innerHTML += "   "; + webauthn_keys.innerHTML += cancel_button; + webauthn_keys.innerHTML += "

"; + webauthn_keys.append(revoke_desc); + webauthn_keys.innerHTML += "

"; + } + + result.keys.forEach((key_info)=>{ + format_key_data(key_info); + }); + } + } + } +} + +async function create_webauthn_key(is_old){ + try { + if (!window.fetch || !navigator.credentials || !navigator.credentials.create) { + throw new Error('Browser not supported.'); + } + + let rep = await window.fetch('api.php?method=getNewKeyArgs§ion=webauthn&token=' + window.token + `&disable_res=${is_old}`, { + method:'GET', + cache:'no-cache', + headers: { + 'Authorization': `Bearer ${window.token}`, + } + }); + let response_server = await rep.json(); + const createArgs = response_server.args; + + if (createArgs == undefined) { + throw new Error("Failed to get authentication arguments."); + } + + recursiveBase64StrToArrayBuffer(createArgs); + + const cred = await navigator.credentials.create(createArgs); + + const authenticatorAttestationResponse = { + transports: cred.response.getTransports ? cred.response.getTransports() : null, + clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null, + attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null + }; + + rep = await window.fetch('api.php?method=createNewKey§ion=webauthn&token=' + window.token, { + method : 'POST', + body : JSON.stringify(authenticatorAttestationResponse), + cache : 'no-cache', + headers : { + 'Authorization': `Bearer ${window.token}`, + } + }); + const authenticatorAttestationServerResponse = await rep.json(); + + // prompt server response + if (authenticatorAttestationServerResponse.result == "OK") { + prepareWebauthn(); + alertify.notify("Ключ был успешно добавлен.", 'success', 5); + + } else { + throw new Error(authenticatorAttestationServerResponse.reason); + } + + } catch (err) { + alertify.notify("Не удалось выполнить запрос! Возможно ваш браузер не поддерживается, или истекло время ожидания!", 'error', 5); + } +} + +function recursiveBase64StrToArrayBuffer(obj) { + let prefix = '=?BINARY?B?'; + let suffix = '?='; + if (typeof obj === 'object') { + for (let key in obj) { + if (typeof obj[key] === 'string') { + let str = obj[key]; + if (str.substring(0, prefix.length) === prefix && str.substring(str.length - suffix.length) === suffix) { + str = str.substring(prefix.length, str.length - suffix.length); + + let binary_string = window.atob(str); + let len = binary_string.length; + let bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + obj[key] = bytes.buffer; + } + } else { + recursiveBase64StrToArrayBuffer(obj[key]); + } + } + } +} + +function arrayBufferToBase64(buffer) { + let binary = ''; + let bytes = new Uint8Array(buffer); + let len = bytes.byteLength; + for (let i = 0; i < len; i++) { + binary += String.fromCharCode( bytes[ i ] ); + } + return window.btoa(binary); +} + function load_admin_stats(){ if(window.is_admin){ var xhr = new XMLHttpRequest(); @@ -708,7 +839,6 @@ function load_admin_stats(){ if(result.result == "OK"){ projects.textContent = result.projects; users.textContent = result.users; - sessions.textContent = result.sessions; requests.textContent = result.requests; } } @@ -872,17 +1002,6 @@ function admin_user_info(user_email){ sec_totp.textContent = "2FA (Enabled)"; } - if(result['easylogin'] != 1){ - sec_easylogin.classList.add('button-secondary'); - sec_easylogin.classList.remove('button-primary'); - sec_easylogin.textContent = "EasyLogin (Disabled)"; - } - else{ - sec_easylogin.classList.remove('button-secondary'); - sec_easylogin.classList.add('button-primary'); - sec_easylogin.textContent = "EasyLogin (Enabled)"; - } - if(result['email_check'] != 1){ sec_emailcheck.classList.add('button-secondary'); sec_emailcheck.classList.remove('button-primary'); @@ -1348,17 +1467,6 @@ function getSecurityInfo(){ totp_state.classList.add('button-secondary'); totp_state.textContent = "Отключить"; } - - if(result.easylogin == 1){ - easylogin_state.classList.remove('button-primary'); - easylogin_state.classList.add('button-secondary'); - easylogin_state.textContent = "Отключить"; - } - else{ - easylogin_state.classList.remove('button-secondary'); - easylogin_state.classList.add('button-primary'); - easylogin_state.textContent = "Включить"; - } } } } @@ -1378,16 +1486,6 @@ function getSecurityInfo(){ totp_state.classList.add('button-secondary'); totp_state.textContent = "Отключить"; } - if(window.security_info.easylogin == 1){ - easylogin_state.classList.remove('button-primary'); - easylogin_state.classList.add('button-secondary'); - easylogin_state.textContent = "Отключить"; - } - else{ - easylogin_state.classList.remove('button-secondary'); - easylogin_state.classList.add('button-primary'); - easylogin_state.textContent = "Включить"; - } } } @@ -1434,53 +1532,6 @@ function manage_email(){ } } -function manage_easylogin(){ - if(window.security_info.easylogin == 1){ - var xhr = new XMLHttpRequest(); - xhr.open('GET', 'api.php?section=easylogin&method=disable', true); - xhr.setRequestHeader("Authorization", "Bearer " + window.token); - xhr.send(); - xhr.onload = function (e) { - if (xhr.readyState == 4 && xhr.status == 200) { - if (xhr.readyState == 4 && xhr.status == 200) { - let result = JSON.parse(xhr.responseText); - if(result.reason == 'RATE_LIMIT_EXCEEDED'){ - window.failed_request = function(){ - manage_easylogin(); - }; - callCaptcha(); - return; - } - window.security_info = undefined; - getSecurityInfo(); - } - } - } - } - else{ - var xhr = new XMLHttpRequest(); - xhr.open('GET', 'api.php?section=easylogin&method=enable', true); - xhr.setRequestHeader("Authorization", "Bearer " + window.token); - xhr.send(); - xhr.onload = function (e) { - if (xhr.readyState == 4 && xhr.status == 200) { - if (xhr.readyState == 4 && xhr.status == 200) { - let result = JSON.parse(xhr.responseText); - if(result.reason == 'RATE_LIMIT_EXCEEDED'){ - window.failed_request = function(){ - manage_easylogin(); - }; - callCaptcha(); - return; - } - window.security_info = undefined; - getSecurityInfo(); - } - } - } - } -} - function prepare_main(){ if(window.user_info == undefined){ var xhr2 = new XMLHttpRequest(); @@ -1689,20 +1740,36 @@ function change_password(old_password, new_password){ function update_user_info(tag, name, surname, bday){ bday = bday / 1000; - if(tag.length < 3 || tag.length > 16){ - alertify.notify('Tag Пользователя не должен быть короче 3 символов!', 'error', 5); + if(tag.length < 3){ + alertify.notify('Tag Пользователя не должен быть короче 3 символов!', 'error', 3); return; } - if(name.length < 2 && name.length > 32){ - alertify.notify('Имя не должно быть короче 2 символов!', 'error', 5); + if(tag.length > 16){ + alertify.notify('Tag Пользователя не должен быть длиннее 16 символов!', 'error', 3); return; } - if(surname.length < 2 && surname.length > 32){ - alertify.notify('Фамилия не должна быть короче 2 символов!', 'error', 5); + + if(name.length < 2){ + alertify.notify('Имя не должно быть короче 2 символов!', 'error', 3); + return; + } + if(name.length > 32){ + alertify.notify('Имя не должно быть длиннее 32 символов!', 'error', 3); return; } + + if(surname.length < 2){ + alertify.notify('Фамилия не должна быть короче 2 символов!', 'error', 3); + return; + } + if(surname.length > 32){ + alertify.notify('Фамилия не должна быть длиннее 32 символов!', 'error', 3); + return; + } + if(bday == 0){ - alertify.notify('Укажите свою дату рождения!', 'error', 5); + alertify.notify('Укажите свою дату рождения!', 'error', 3); + return; } xhr2.open('GET', '/api.php?section=register&method=saveInfo&user_name=' + encodeURIComponent(name) + '&user_surname=' + encodeURIComponent(surname) + '&user_nick=' + encodeURIComponent(tag) + '&birthday=' + encodeURIComponent(bday), true); @@ -1711,27 +1778,27 @@ function update_user_info(tag, name, surname, bday){ xhr2.onload = function (e) { let ret = JSON.parse(xhr2.responseText); if(ret.result == "FAULT"){ - if(result.reason == 'RATE_LIMIT_EXCEEDED'){ + if(ret.reason == 'RATE_LIMIT_EXCEEDED'){ window.failed_request = function(){ update_user_info(tag, name, surname, bday); }; callCaptcha(); } if(ret.reason == "MALFORMED_NICK"){ - alertify.notify('Tag Пользователя занят, либо содержит запрещённые знаки!', 'error', 5); + alertify.notify('Tag Пользователя занят, либо содержит запрещённые знаки!', 'error', 3); } if(ret.reason == "MALFORMED_NAME"){ - alertify.notify('Имя содержит запрещённые знаки!', 'error', 5); + alertify.notify('Имя содержит запрещённые знаки!', 'error', 3); } if(ret.reason == "MALFORMED_SURNAME"){ - alertify.notify('Фамилия содержит запрещённые знаки!', 'error', 5); + alertify.notify('Фамилия содержит запрещённые знаки!', 'error', 3); } if(ret.reason == "MALFORMED_BIRTHDAY"){ - alertify.notify('Дата Рождения не указана!', 'error', 5); + alertify.notify('Дата Рождения не указана!', 'error', 3); } } else{ - alertify.notify('Данные пользователя были обновлены!', 'success', 5); + alertify.notify('Данные пользователя были обновлены!', 'success', 3); window.user_info = undefined; checkAPIToken(); } diff --git a/index.php b/index.php index 3e5dae3..93bc974 100644 --- a/index.php +++ b/index.php @@ -18,12 +18,12 @@

Сервис недоступен!

В текущий момент сервис недоступен. Подробнее:

-

Status Page

-

Извините за причинённые неудобства!

+

Status Page

+

Приносим извинения за доставленные неудобства!

Ваш EMail - +

@@ -53,7 +53,7 @@
- +
@@ -77,17 +77,6 @@ - - \ No newline at end of file diff --git a/libs/email_handler.php b/libs/email_handler.php index e337d37..2dda020 100644 --- a/libs/email_handler.php +++ b/libs/email_handler.php @@ -4,9 +4,19 @@ class email{ public function __construct($email_settings){ + global $disable_email; + if($disable_email){ + returnError("UNABLE_TO_INIT_EMAIL"); + } $config = [ 'defaultFrom' => $email_settings['messageFrom'], - 'onError' => function($error, $message, $transport) { echo $error; }, + 'onError' => function($error, $message, $transport) { + global $email_settings; + if ($email_settings['email_debug']){ + returnError($error); + } + returnError("EMAIL_DELIVERY_FAULT"); + }, 'afterSend' => function($text, $message, $layer) { $nothing = 0; }, 'transports' => [ ['smtp', 'host' => $email_settings['smtp'], 'ssl' => true, 'port' => $email_settings['port'], 'login' => $email_settings['login'], 'password' => $email_settings['password']] diff --git a/libs/gen_2fa_qr.php b/libs/gen_2fa_qr.php index 2ca84d7..4c7d34e 100644 --- a/libs/gen_2fa_qr.php +++ b/libs/gen_2fa_qr.php @@ -30,10 +30,4 @@ function remove_spaces($string){ $QR->png($QR_Base); } -if($_GET['method'] == "EasyLoginSession"){ - $QR_Base = base64_decode($_GET['session']); - - $QR->png($QR_Base); -} - ?> \ No newline at end of file diff --git a/libs/qr_reader.min.js b/libs/qr_reader.min.js deleted file mode 100644 index b70dad3..0000000 --- a/libs/qr_reader.min.js +++ /dev/null @@ -1,8 +0,0 @@ -/*! For license information please see html5-qrcode.min.js.LICENSE.txt */ -var __Html5QrcodeLibrary__;(()=>{var t={449:function(t,e,r){!function(t){"use strict";var e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r])};var n,i=function(t){function r(e){var r,n,i,o=this.constructor,s=t.call(this,e)||this;return Object.defineProperty(s,"name",{value:o.name,enumerable:!1}),r=s,n=o.prototype,(i=Object.setPrototypeOf)?i(r,n):r.__proto__=n,function(t,e){void 0===e&&(e=t.constructor);var r=Error.captureStackTrace;r&&r(t,e)}(s),s}return function(t,r){function n(){this.constructor=t}e(t,r),t.prototype=null===r?Object.create(r):(n.prototype=r.prototype,new n)}(r,t),r}(Error);class o extends i{constructor(t){super(t),this.message=t}getKind(){return this.constructor.kind}}o.kind="Exception";class s extends o{}s.kind="ArgumentException";class a extends o{}a.kind="IllegalArgumentException";class l{constructor(t){if(this.binarizer=t,null===t)throw new a("Binarizer must be non-null.")}getWidth(){return this.binarizer.getWidth()}getHeight(){return this.binarizer.getHeight()}getBlackRow(t,e){return this.binarizer.getBlackRow(t,e)}getBlackMatrix(){return null!==this.matrix&&void 0!==this.matrix||(this.matrix=this.binarizer.getBlackMatrix()),this.matrix}isCropSupported(){return this.binarizer.getLuminanceSource().isCropSupported()}crop(t,e,r,n){const i=this.binarizer.getLuminanceSource().crop(t,e,r,n);return new l(this.binarizer.createBinarizer(i))}isRotateSupported(){return this.binarizer.getLuminanceSource().isRotateSupported()}rotateCounterClockwise(){const t=this.binarizer.getLuminanceSource().rotateCounterClockwise();return new l(this.binarizer.createBinarizer(t))}rotateCounterClockwise45(){const t=this.binarizer.getLuminanceSource().rotateCounterClockwise45();return new l(this.binarizer.createBinarizer(t))}toString(){try{return this.getBlackMatrix().toString()}catch(t){return""}}}class c extends o{static getChecksumInstance(){return new c}}c.kind="ChecksumException";class h{constructor(t){this.source=t}getLuminanceSource(){return this.source}getWidth(){return this.source.getWidth()}getHeight(){return this.source.getHeight()}}class u{static arraycopy(t,e,r,n,i){for(;i--;)r[n++]=t[e++]}static currentTimeMillis(){return Date.now()}}class d extends o{}d.kind="IndexOutOfBoundsException";class g extends d{constructor(t,e){super(e),this.index=t,this.message=e}}g.kind="ArrayIndexOutOfBoundsException";class f{static fill(t,e){for(let r=0,n=t.length;rr)throw new a("fromIndex("+e+") > toIndex("+r+")");if(e<0)throw new g(e);if(r>t)throw new g(r)}static asList(...t){return t}static create(t,e,r){return Array.from({length:t}).map((t=>Array.from({length:e}).fill(r)))}static createInt32Array(t,e,r){return Array.from({length:t}).map((t=>Int32Array.from({length:e}).fill(r)))}static equals(t,e){if(!t)return!1;if(!e)return!1;if(!t.length)return!1;if(!e.length)return!1;if(t.length!==e.length)return!1;for(let r=0,n=t.length;r>1,s=r(e,t[o]);if(s>0)n=o+1;else{if(!(s<0))return o;i=o-1}}return-n-1}static numberComparator(t,e){return t-e}}class w{static numberOfTrailingZeros(t){let e;if(0===t)return 32;let r=31;return e=t<<16,0!==e&&(r-=16,t=e),e=t<<8,0!==e&&(r-=8,t=e),e=t<<4,0!==e&&(r-=4,t=e),e=t<<2,0!==e&&(r-=2,t=e),r-(t<<1>>>31)}static numberOfLeadingZeros(t){if(0===t)return 32;let e=1;return t>>>16==0&&(e+=16,t<<=16),t>>>24==0&&(e+=8,t<<=8),t>>>28==0&&(e+=4,t<<=4),t>>>30==0&&(e+=2,t<<=2),e-=t>>>31,e}static toHexString(t){return t.toString(16)}static toBinaryString(t){return String(parseInt(String(t),2))}static bitCount(t){return t=(t=(858993459&(t-=t>>>1&1431655765))+(t>>>2&858993459))+(t>>>4)&252645135,63&(t+=t>>>8)+(t>>>16)}static truncDivision(t,e){return Math.trunc(t/e)}static parseInt(t,e){return parseInt(t,e)}}w.MIN_VALUE_32_BITS=-2147483648,w.MAX_VALUE=Number.MAX_SAFE_INTEGER;class A{constructor(t,e){void 0===t?(this.size=0,this.bits=new Int32Array(1)):(this.size=t,this.bits=null==e?A.makeArray(t):e)}getSize(){return this.size}getSizeInBytes(){return Math.floor((this.size+7)/8)}ensureCapacity(t){if(t>32*this.bits.length){const e=A.makeArray(t);u.arraycopy(this.bits,0,e,0,this.bits.length),this.bits=e}}get(t){return 0!=(this.bits[Math.floor(t/32)]&1<<(31&t))}set(t){this.bits[Math.floor(t/32)]|=1<<(31&t)}flip(t){this.bits[Math.floor(t/32)]^=1<<(31&t)}getNextSet(t){const e=this.size;if(t>=e)return e;const r=this.bits;let n=Math.floor(t/32),i=r[n];i&=~((1<<(31&t))-1);const o=r.length;for(;0===i;){if(++n===o)return e;i=r[n]}const s=32*n+w.numberOfTrailingZeros(i);return s>e?e:s}getNextUnset(t){const e=this.size;if(t>=e)return e;const r=this.bits;let n=Math.floor(t/32),i=~r[n];i&=~((1<<(31&t))-1);const o=r.length;for(;0===i;){if(++n===o)return e;i=~r[n]}const s=32*n+w.numberOfTrailingZeros(i);return s>e?e:s}setBulk(t,e){this.bits[Math.floor(t/32)]=e}setRange(t,e){if(ethis.size)throw new a;if(e===t)return;e--;const r=Math.floor(t/32),n=Math.floor(e/32),i=this.bits;for(let o=r;o<=n;o++){const s=(2<<(or?0:31&t));i[o]|=s}}clear(){const t=this.bits.length,e=this.bits;for(let r=0;rthis.size)throw new a;if(e===t)return!0;e--;const n=Math.floor(t/32),i=Math.floor(e/32),o=this.bits;for(let s=n;s<=i;s++){const a=(2<<(sn?0:31&t))&4294967295;if((o[s]&a)!==(r?a:0))return!1}return!0}appendBit(t){this.ensureCapacity(this.size+1),t&&(this.bits[Math.floor(this.size/32)]|=1<<(31&this.size)),this.size++}appendBits(t,e){if(e<0||e>32)throw new a("Num bits must be between 0 and 32");this.ensureCapacity(this.size+e);for(let r=e;r>0;r--)this.appendBit(1==(t>>r-1&1))}appendBitArray(t){const e=t.size;this.ensureCapacity(this.size+e);for(let r=0;r>1&1431655765|(1431655765&r)<<1,r=r>>2&858993459|(858993459&r)<<2,r=r>>4&252645135|(252645135&r)<<4,r=r>>8&16711935|(16711935&r)<<8,r=r>>16&65535|(65535&r)<<16,t[e-i]=r}if(this.size!==32*r){const e=32*r-this.size;let n=t[0]>>>e;for(let i=1;i>>e}t[r-1]=n}this.bits=t}static makeArray(t){return new Int32Array(Math.floor((t+31)/32))}equals(t){if(!(t instanceof A))return!1;const e=t;return this.size===e.size&&f.equals(this.bits,e.bits)}hashCode(){return 31*this.size+f.hashCode(this.bits)}toString(){let t="";for(let e=0,r=this.size;e=900)throw new C("incorect value");const e=I.VALUES_TO_ECI.get(t);if(void 0===e)throw new C("incorect value");return e}static getCharacterSetECIByName(t){const e=I.NAME_TO_ECI.get(t);if(void 0===e)throw new C("incorect value");return e}equals(t){if(!(t instanceof I))return!1;const e=t;return this.getName()===e.getName()}}I.VALUE_IDENTIFIER_TO_ECI=new Map,I.VALUES_TO_ECI=new Map,I.NAME_TO_ECI=new Map,I.Cp437=new I(m.Cp437,Int32Array.from([0,2]),"Cp437"),I.ISO8859_1=new I(m.ISO8859_1,Int32Array.from([1,3]),"ISO-8859-1","ISO88591","ISO8859_1"),I.ISO8859_2=new I(m.ISO8859_2,4,"ISO-8859-2","ISO88592","ISO8859_2"),I.ISO8859_3=new I(m.ISO8859_3,5,"ISO-8859-3","ISO88593","ISO8859_3"),I.ISO8859_4=new I(m.ISO8859_4,6,"ISO-8859-4","ISO88594","ISO8859_4"),I.ISO8859_5=new I(m.ISO8859_5,7,"ISO-8859-5","ISO88595","ISO8859_5"),I.ISO8859_6=new I(m.ISO8859_6,8,"ISO-8859-6","ISO88596","ISO8859_6"),I.ISO8859_7=new I(m.ISO8859_7,9,"ISO-8859-7","ISO88597","ISO8859_7"),I.ISO8859_8=new I(m.ISO8859_8,10,"ISO-8859-8","ISO88598","ISO8859_8"),I.ISO8859_9=new I(m.ISO8859_9,11,"ISO-8859-9","ISO88599","ISO8859_9"),I.ISO8859_10=new I(m.ISO8859_10,12,"ISO-8859-10","ISO885910","ISO8859_10"),I.ISO8859_11=new I(m.ISO8859_11,13,"ISO-8859-11","ISO885911","ISO8859_11"),I.ISO8859_13=new I(m.ISO8859_13,15,"ISO-8859-13","ISO885913","ISO8859_13"),I.ISO8859_14=new I(m.ISO8859_14,16,"ISO-8859-14","ISO885914","ISO8859_14"),I.ISO8859_15=new I(m.ISO8859_15,17,"ISO-8859-15","ISO885915","ISO8859_15"),I.ISO8859_16=new I(m.ISO8859_16,18,"ISO-8859-16","ISO885916","ISO8859_16"),I.SJIS=new I(m.SJIS,20,"SJIS","Shift_JIS"),I.Cp1250=new I(m.Cp1250,21,"Cp1250","windows-1250"),I.Cp1251=new I(m.Cp1251,22,"Cp1251","windows-1251"),I.Cp1252=new I(m.Cp1252,23,"Cp1252","windows-1252"),I.Cp1256=new I(m.Cp1256,24,"Cp1256","windows-1256"),I.UnicodeBigUnmarked=new I(m.UnicodeBigUnmarked,25,"UnicodeBigUnmarked","UTF-16BE","UnicodeBig"),I.UTF8=new I(m.UTF8,26,"UTF8","UTF-8"),I.ASCII=new I(m.ASCII,Int32Array.from([27,170]),"ASCII","US-ASCII"),I.Big5=new I(m.Big5,28,"Big5"),I.GB18030=new I(m.GB18030,29,"GB18030","GB2312","EUC_CN","GBK"),I.EUC_KR=new I(m.EUC_KR,30,"EUC_KR","EUC-KR");class _ extends o{}_.kind="UnsupportedOperationException";class p{static decode(t,e){const r=this.encodingName(e);return this.customDecoder?this.customDecoder(t,r):"undefined"==typeof TextDecoder||this.shouldDecodeOnFallback(r)?this.decodeFallback(t,r):new TextDecoder(r).decode(t)}static shouldDecodeOnFallback(t){return!p.isBrowser()&&"ISO-8859-1"===t}static encode(t,e){const r=this.encodingName(e);return this.customEncoder?this.customEncoder(t,r):"undefined"==typeof TextEncoder?this.encodeFallback(t):(new TextEncoder).encode(t)}static isBrowser(){return"undefined"!=typeof window&&"[object Window]"==={}.toString.call(window)}static encodingName(t){return"string"==typeof t?t:t.getName()}static encodingCharacterSet(t){return t instanceof I?t:I.getCharacterSetECIByName(t)}static decodeFallback(t,e){const r=this.encodingCharacterSet(e);if(p.isDecodeFallbackSupported(r)){let e="";for(let r=0,n=t.length;r3&&239===t[0]&&187===t[1]&&191===t[2];for(let e=0;e0?0==(128&r)?o=!1:s--:0!=(128&r)&&(0==(64&r)?o=!1:(s++,0==(32&r)?a++:(s++,0==(16&r)?l++:(s++,0==(8&r)?c++:o=!1))))),n&&(r>127&&r<160?n=!1:r>159&&(r<192||215===r||247===r)&&A++),i&&(h>0?r<64||127===r||r>252?i=!1:h--:128===r||160===r||r>239?i=!1:r>160&&r<224?(u++,g=0,d++,d>f&&(f=d)):r>127?(h++,d=0,g++,g>w&&(w=g)):(d=0,g=0))}return o&&s>0&&(o=!1),i&&h>0&&(i=!1),o&&(m||a+l+c>0)?S.UTF8:i&&(S.ASSUME_SHIFT_JIS||f>=3||w>=3)?S.SHIFT_JIS:n&&i?2===f&&2===u||10*A>=r?S.SHIFT_JIS:S.ISO88591:n?S.ISO88591:i?S.SHIFT_JIS:o?S.UTF8:S.PLATFORM_DEFAULT_ENCODING}static format(t,...e){let r=-1;return t.replace(/%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd%])/g,(function(t,n,i,o,s,a){if("%%"===t)return"%";if(void 0===e[++r])return;t=o?parseInt(o.substr(1)):void 0;let l,c=s?parseInt(s.substr(1)):void 0;switch(a){case"s":l=e[r];break;case"c":l=e[r][0];break;case"f":l=parseFloat(e[r]).toFixed(t);break;case"p":l=parseFloat(e[r]).toPrecision(t);break;case"e":l=parseFloat(e[r]).toExponential(t);break;case"x":l=parseInt(e[r]).toString(c||16);break;case"d":l=parseFloat(parseInt(e[r],c||10).toPrecision(t)).toFixed(0)}l="object"==typeof l?JSON.stringify(l):(+l).toString(c);let h=parseInt(i),u=i&&i[0]+""=="0"?"0":" ";for(;l.lengtho){if(-1===s)s=i-o;else if(i-o!==s)throw new a("row lengths do not match");o=i,l++}c++}else if(t.substring(c,c+e.length)===e)c+=e.length,n[i]=!0,i++;else{if(t.substring(c,c+r.length)!==r)throw new a("illegal character encountered: "+t.substring(c));c+=r.length,n[i]=!1,i++}if(i>o){if(-1===s)s=i-o;else if(i-o!==s)throw new a("row lengths do not match");l++}const h=new N(s,l);for(let t=0;t>>(31&t)&1)}set(t,e){const r=e*this.rowSize+Math.floor(t/32);this.bits[r]|=1<<(31&t)&4294967295}unset(t,e){const r=e*this.rowSize+Math.floor(t/32);this.bits[r]&=~(1<<(31&t)&4294967295)}flip(t,e){const r=e*this.rowSize+Math.floor(t/32);this.bits[r]^=1<<(31&t)&4294967295}xor(t){if(this.width!==t.getWidth()||this.height!==t.getHeight()||this.rowSize!==t.getRowSize())throw new a("input matrix dimensions do not match");const e=new A(Math.floor(this.width/32)+1),r=this.rowSize,n=this.bits;for(let i=0,o=this.height;ithis.height||i>this.width)throw new a("The region must fit inside the matrix");const s=this.rowSize,l=this.bits;for(let r=e;ra&&(a=t),32*es){let t=31;for(;l>>>t==0;)t--;32*e+t>s&&(s=32*e+t)}}}return s=0&&0===e[r];)r--;if(r<0)return null;const n=Math.floor(r/t);let i=32*Math.floor(r%t);const o=e[r];let s=31;for(;o>>>s==0;)s--;return i+=s,Int32Array.from([i,n])}getWidth(){return this.width}getHeight(){return this.height}getRowSize(){return this.rowSize}equals(t){if(!(t instanceof N))return!1;const e=t;return this.width===e.width&&this.height===e.height&&this.rowSize===e.rowSize&&f.equals(this.bits,e.bits)}hashCode(){let t=this.width;return t=31*t+this.width,t=31*t+this.height,t=31*t+this.rowSize,t=31*t+f.hashCode(this.bits),t}toString(t="X ",e=" ",r="\n"){return this.buildToString(t,e,r)}buildToString(t,e,r){let n=new T;for(let i=0,o=this.height;i>M.LUMINANCE_SHIFT]++;const s=M.estimateBlackPoint(o);if(n<3)for(let t=0;t>M.LUMINANCE_SHIFT]++}const o=M.estimateBlackPoint(i),s=t.getMatrix();for(let t=0;ti&&(n=o,i=t[o]),t[o]>r&&(r=t[o]);let o=0,s=0;for(let r=0;rs&&(o=r,s=i)}if(n>o){const t=n;n=o,o=t}if(o-n<=e/16)throw new y;let a=o-1,l=-1;for(let e=o-1;e>n;e--){const i=e-n,s=i*i*(o-e)*(r-t[e]);s>l&&(a=e,l=s)}return a<=D.MINIMUM_DIMENSION&&r>=D.MINIMUM_DIMENSION){const n=t.getMatrix();let i=e>>D.BLOCK_SIZE_POWER;0!=(e&D.BLOCK_SIZE_MASK)&&i++;let o=r>>D.BLOCK_SIZE_POWER;0!=(r&D.BLOCK_SIZE_MASK)&&o++;const s=D.calculateBlackPoints(n,i,o,e,r),a=new N(e,r);D.calculateThresholdForBlock(n,i,o,e,r,s,a),this.matrix=a}else this.matrix=super.getBlackMatrix();return this.matrix}createBinarizer(t){return new D(t)}static calculateThresholdForBlock(t,e,r,n,i,o,s){const a=i-D.BLOCK_SIZE,l=n-D.BLOCK_SIZE;for(let i=0;ia&&(c=a);const h=D.cap(i,2,r-3);for(let r=0;rl&&(i=l);const a=D.cap(r,2,e-3);let u=0;for(let t=-2;t<=2;t++){const e=o[h+t];u+=e[a-2]+e[a-1]+e[a]+e[a+1]+e[a+2]}const d=u/25;D.thresholdBlock(t,i,c,d,n,s)}}}static cap(t,e,r){return tr?r:t}static thresholdBlock(t,e,r,n,i,o){for(let s=0,a=r*i+e;so&&(r=o);for(let o=0;os&&(e=s);let l=0,c=255,h=0;for(let i=0,o=r*n+e;ih&&(h=r)}if(h-c>D.MIN_DYNAMIC_RANGE)for(i++,o+=n;i>2*D.BLOCK_SIZE_POWER;if(h-c<=D.MIN_DYNAMIC_RANGE&&(u=c/2,i>0&&o>0)){const t=(a[i-1][o]+2*a[i][o-1]+a[i-1][o-1])/4;c>10,n[r]=i}return n}getRow(t,e){if(t<0||t>=this.getHeight())throw new a("Requested row is outside the image: "+t);const r=this.getWidth(),n=t*r;return null===e?e=this.buffer.slice(n,n+r):(e.lengthnew L(t.deviceId,t.label)))}))}findDeviceById(t){return P(this,void 0,void 0,(function*(){const e=yield this.listVideoInputDevices();return e?e.find((e=>e.deviceId===t)):null}))}decodeFromInputVideoDevice(t,e){return P(this,void 0,void 0,(function*(){return yield this.decodeOnceFromVideoDevice(t,e)}))}decodeOnceFromVideoDevice(t,e){return P(this,void 0,void 0,(function*(){let r;this.reset(),r=t?{deviceId:{exact:t}}:{facingMode:"environment"};const n={video:r};return yield this.decodeOnceFromConstraints(n,e)}))}decodeOnceFromConstraints(t,e){return P(this,void 0,void 0,(function*(){const r=yield navigator.mediaDevices.getUserMedia(t);return yield this.decodeOnceFromStream(r,e)}))}decodeOnceFromStream(t,e){return P(this,void 0,void 0,(function*(){this.reset();const r=yield this.attachStreamToVideo(t,e);return yield this.decodeOnce(r)}))}decodeFromInputVideoDeviceContinuously(t,e,r){return P(this,void 0,void 0,(function*(){return yield this.decodeFromVideoDevice(t,e,r)}))}decodeFromVideoDevice(t,e,r){return P(this,void 0,void 0,(function*(){let n;n=t?{deviceId:{exact:t}}:{facingMode:"environment"};const i={video:n};return yield this.decodeFromConstraints(i,e,r)}))}decodeFromConstraints(t,e,r){return P(this,void 0,void 0,(function*(){const n=yield navigator.mediaDevices.getUserMedia(t);return yield this.decodeFromStream(n,e,r)}))}decodeFromStream(t,e,r){return P(this,void 0,void 0,(function*(){this.reset();const n=yield this.attachStreamToVideo(t,e);return yield this.decodeContinuously(n,r)}))}stopAsyncDecode(){this._stopAsyncDecode=!0}stopContinuousDecode(){this._stopContinuousDecode=!0}attachStreamToVideo(t,e){return P(this,void 0,void 0,(function*(){const r=this.prepareVideoElement(e);return this.addVideoSource(r,t),this.videoElement=r,this.stream=t,yield this.playVideoOnLoadAsync(r),r}))}playVideoOnLoadAsync(t){return new Promise(((e,r)=>this.playVideoOnLoad(t,(()=>e()))))}playVideoOnLoad(t,e){this.videoEndedListener=()=>this.stopStreams(),this.videoCanPlayListener=()=>this.tryPlayVideo(t),t.addEventListener("ended",this.videoEndedListener),t.addEventListener("canplay",this.videoCanPlayListener),t.addEventListener("playing",e),this.tryPlayVideo(t)}isVideoPlaying(t){return t.currentTime>0&&!t.paused&&!t.ended&&t.readyState>2}tryPlayVideo(t){return P(this,void 0,void 0,(function*(){if(this.isVideoPlaying(t))console.warn("Trying to play video that is already playing.");else try{yield t.play()}catch(t){console.warn("It was not possible to play the video.")}}))}getMediaElement(t,e){const r=document.getElementById(t);if(!r)throw new s(`element with id '${t}' not found`);if(r.nodeName.toLowerCase()!==e.toLowerCase())throw new s(`element with id '${t}' must be an ${e} element`);return r}decodeFromImage(t,e){if(!t&&!e)throw new s("either imageElement with a src set or an url must be provided");return e&&!t?this.decodeFromImageUrl(e):this.decodeFromImageElement(t)}decodeFromVideo(t,e){if(!t&&!e)throw new s("Either an element with a src set or an URL must be provided");return e&&!t?this.decodeFromVideoUrl(e):this.decodeFromVideoElement(t)}decodeFromVideoContinuously(t,e,r){if(void 0===t&&void 0===e)throw new s("Either an element with a src set or an URL must be provided");return e&&!t?this.decodeFromVideoUrlContinuously(e,r):this.decodeFromVideoElementContinuously(t,r)}decodeFromImageElement(t){if(!t)throw new s("An image element must be provided.");this.reset();const e=this.prepareImageElement(t);let r;return this.imageElement=e,r=this.isImageLoaded(e)?this.decodeOnce(e,!1,!0):this._decodeOnLoadImage(e),r}decodeFromVideoElement(t){const e=this._decodeFromVideoElementSetup(t);return this._decodeOnLoadVideo(e)}decodeFromVideoElementContinuously(t,e){const r=this._decodeFromVideoElementSetup(t);return this._decodeOnLoadVideoContinuously(r,e)}_decodeFromVideoElementSetup(t){if(!t)throw new s("A video element must be provided.");this.reset();const e=this.prepareVideoElement(t);return this.videoElement=e,e}decodeFromImageUrl(t){if(!t)throw new s("An URL must be provided.");this.reset();const e=this.prepareImageElement();this.imageElement=e;const r=this._decodeOnLoadImage(e);return e.src=t,r}decodeFromVideoUrl(t){if(!t)throw new s("An URL must be provided.");this.reset();const e=this.prepareVideoElement(),r=this.decodeFromVideoElement(e);return e.src=t,r}decodeFromVideoUrlContinuously(t,e){if(!t)throw new s("An URL must be provided.");this.reset();const r=this.prepareVideoElement(),n=this.decodeFromVideoElementContinuously(r,e);return r.src=t,n}_decodeOnLoadImage(t){return new Promise(((e,r)=>{this.imageLoadedListener=()=>this.decodeOnce(t,!1,!0).then(e,r),t.addEventListener("load",this.imageLoadedListener)}))}_decodeOnLoadVideo(t){return P(this,void 0,void 0,(function*(){return yield this.playVideoOnLoadAsync(t),yield this.decodeOnce(t)}))}_decodeOnLoadVideoContinuously(t,e){return P(this,void 0,void 0,(function*(){yield this.playVideoOnLoadAsync(t),this.decodeContinuously(t,e)}))}isImageLoaded(t){return!!t.complete&&0!==t.naturalWidth}prepareImageElement(t){let e;return void 0===t&&(e=document.createElement("img"),e.width=200,e.height=200),"string"==typeof t&&(e=this.getMediaElement(t,"img")),t instanceof HTMLImageElement&&(e=t),e}prepareVideoElement(t){let e;return t||"undefined"==typeof document||(e=document.createElement("video"),e.width=200,e.height=200),"string"==typeof t&&(e=this.getMediaElement(t,"video")),t instanceof HTMLVideoElement&&(e=t),e.setAttribute("autoplay","true"),e.setAttribute("muted","true"),e.setAttribute("playsinline","true"),e}decodeOnce(t,e=!0,r=!0){this._stopAsyncDecode=!1;const n=(i,o)=>{if(this._stopAsyncDecode)return o(new y("Video stream has ended before any code could be detected.")),void(this._stopAsyncDecode=void 0);try{i(this.decode(t))}catch(t){const s=(t instanceof c||t instanceof C)&&r;if(e&&t instanceof y||s)return setTimeout(n,this._timeBetweenDecodingAttempts,i,o);o(t)}};return new Promise(((t,e)=>n(t,e)))}decodeContinuously(t,e){this._stopContinuousDecode=!1;const r=()=>{if(this._stopContinuousDecode)this._stopContinuousDecode=void 0;else try{const n=this.decode(t);e(n,null),setTimeout(r,this.timeBetweenScansMillis)}catch(t){e(null,t);const n=t instanceof y;(t instanceof c||t instanceof C||n)&&setTimeout(r,this._timeBetweenDecodingAttempts)}};r()}decode(t){const e=this.createBinaryBitmap(t);return this.decodeBitmap(e)}createBinaryBitmap(t){const e=this.getCaptureCanvasContext(t);this.drawImageOnCanvas(e,t);const r=this.getCaptureCanvas(t),n=new b(r),i=new D(n);return new l(i)}getCaptureCanvasContext(t){if(!this.captureCanvasContext){const e=this.getCaptureCanvas(t).getContext("2d");this.captureCanvasContext=e}return this.captureCanvasContext}getCaptureCanvas(t){if(!this.captureCanvas){const e=this.createCaptureCanvas(t);this.captureCanvas=e}return this.captureCanvas}drawImageOnCanvas(t,e){t.drawImage(e,0,0)}decodeBitmap(t){return this.reader.decode(t,this._hints)}createCaptureCanvas(t){if("undefined"==typeof document)return this._destroyCaptureCanvas(),null;const e=document.createElement("canvas");let r,n;return void 0!==t&&(t instanceof HTMLVideoElement?(r=t.videoWidth,n=t.videoHeight):t instanceof HTMLImageElement&&(r=t.naturalWidth||t.width,n=t.naturalHeight||t.height)),e.style.width=r+"px",e.style.height=n+"px",e.width=r,e.height=n,e}stopStreams(){this.stream&&(this.stream.getVideoTracks().forEach((t=>t.stop())),this.stream=void 0),!1===this._stopAsyncDecode&&this.stopAsyncDecode(),!1===this._stopContinuousDecode&&this.stopContinuousDecode()}reset(){this.stopStreams(),this._destroyVideoElement(),this._destroyImageElement(),this._destroyCaptureCanvas()}_destroyVideoElement(){this.videoElement&&(void 0!==this.videoEndedListener&&this.videoElement.removeEventListener("ended",this.videoEndedListener),void 0!==this.videoPlayingEventListener&&this.videoElement.removeEventListener("playing",this.videoPlayingEventListener),void 0!==this.videoCanPlayListener&&this.videoElement.removeEventListener("loadedmetadata",this.videoCanPlayListener),this.cleanVideoSource(this.videoElement),this.videoElement=void 0)}_destroyImageElement(){this.imageElement&&(void 0!==this.imageLoadedListener&&this.imageElement.removeEventListener("load",this.imageLoadedListener),this.imageElement.src=void 0,this.imageElement.removeAttribute("src"),this.imageElement=void 0)}_destroyCaptureCanvas(){this.captureCanvasContext=void 0,this.captureCanvas=void 0}addVideoSource(t,e){try{t.srcObject=e}catch(r){t.src=URL.createObjectURL(e)}}cleanVideoSource(t){try{t.srcObject=null}catch(e){t.src=""}this.videoElement.removeAttribute("src")}}class F{constructor(t,e,r=(null==e?0:8*e.length),n,i,o=u.currentTimeMillis()){this.text=t,this.rawBytes=e,this.numBits=r,this.resultPoints=n,this.format=i,this.timestamp=o,this.text=t,this.rawBytes=e,this.numBits=null==r?null==e?0:8*e.length:r,this.resultPoints=n,this.format=i,this.resultMetadata=null,this.timestamp=null==o?u.currentTimeMillis():o}getText(){return this.text}getRawBytes(){return this.rawBytes}getNumBits(){return this.numBits}getResultPoints(){return this.resultPoints}getBarcodeFormat(){return this.format}getResultMetadata(){return this.resultMetadata}putMetadata(t,e){null===this.resultMetadata&&(this.resultMetadata=new Map),this.resultMetadata.set(t,e)}putAllMetadata(t){null!==t&&(null===this.resultMetadata?this.resultMetadata=t:this.resultMetadata=new Map(t))}addResultPoints(t){const e=this.resultPoints;if(null===e)this.resultPoints=t;else if(null!==t&&t.length>0){const r=new Array(e.length+t.length);u.arraycopy(e,0,r,0,e.length),u.arraycopy(t,0,r,e.length,t.length),this.resultPoints=r}}getTimestamp(){return this.timestamp}toString(){return this.text}}!function(t){t[t.AZTEC=0]="AZTEC",t[t.CODABAR=1]="CODABAR",t[t.CODE_39=2]="CODE_39",t[t.CODE_93=3]="CODE_93",t[t.CODE_128=4]="CODE_128",t[t.DATA_MATRIX=5]="DATA_MATRIX",t[t.EAN_8=6]="EAN_8",t[t.EAN_13=7]="EAN_13",t[t.ITF=8]="ITF",t[t.MAXICODE=9]="MAXICODE",t[t.PDF_417=10]="PDF_417",t[t.QR_CODE=11]="QR_CODE",t[t.RSS_14=12]="RSS_14",t[t.RSS_EXPANDED=13]="RSS_EXPANDED",t[t.UPC_A=14]="UPC_A",t[t.UPC_E=15]="UPC_E",t[t.UPC_EAN_EXTENSION=16]="UPC_EAN_EXTENSION"}(B||(B={}));var x,k=B;!function(t){t[t.OTHER=0]="OTHER",t[t.ORIENTATION=1]="ORIENTATION",t[t.BYTE_SEGMENTS=2]="BYTE_SEGMENTS",t[t.ERROR_CORRECTION_LEVEL=3]="ERROR_CORRECTION_LEVEL",t[t.ISSUE_NUMBER=4]="ISSUE_NUMBER",t[t.SUGGESTED_PRICE=5]="SUGGESTED_PRICE",t[t.POSSIBLE_COUNTRY=6]="POSSIBLE_COUNTRY",t[t.UPC_EAN_EXTENSION=7]="UPC_EAN_EXTENSION",t[t.PDF417_EXTRA_METADATA=8]="PDF417_EXTRA_METADATA",t[t.STRUCTURED_APPEND_SEQUENCE=9]="STRUCTURED_APPEND_SEQUENCE",t[t.STRUCTURED_APPEND_PARITY=10]="STRUCTURED_APPEND_PARITY"}(x||(x={}));var U,H,V,z,G,Y,X=x;class W{constructor(t,e,r,n,i=-1,o=-1){this.rawBytes=t,this.text=e,this.byteSegments=r,this.ecLevel=n,this.structuredAppendSequenceNumber=i,this.structuredAppendParity=o,this.numBits=null==t?0:8*t.length}getRawBytes(){return this.rawBytes}getNumBits(){return this.numBits}setNumBits(t){this.numBits=t}getText(){return this.text}getByteSegments(){return this.byteSegments}getECLevel(){return this.ecLevel}getErrorsCorrected(){return this.errorsCorrected}setErrorsCorrected(t){this.errorsCorrected=t}getErasures(){return this.erasures}setErasures(t){this.erasures=t}getOther(){return this.other}setOther(t){this.other=t}hasStructuredAppend(){return this.structuredAppendParity>=0&&this.structuredAppendSequenceNumber>=0}getStructuredAppendParity(){return this.structuredAppendParity}getStructuredAppendSequenceNumber(){return this.structuredAppendSequenceNumber}}class j{exp(t){return this.expTable[t]}log(t){if(0===t)throw new a;return this.logTable[t]}static addOrSubtract(t,e){return t^e}}class Z{constructor(t,e){if(0===e.length)throw new a;this.field=t;const r=e.length;if(r>1&&0===e[0]){let t=1;for(;tr.length){const t=e;e=r,r=t}let n=new Int32Array(r.length);const i=r.length-e.length;u.arraycopy(r,0,n,0,i);for(let t=i;t=t.getDegree()&&!n.isZero();){const i=n.getDegree()-t.getDegree(),s=e.multiply(n.getCoefficient(n.getDegree()),o),a=t.multiplyByMonomial(i,s),l=e.buildMonomial(i,s);r=r.addOrSubtract(l),n=n.addOrSubtract(a)}return[r,n]}toString(){let t="";for(let e=this.getDegree();e>=0;e--){let r=this.getCoefficient(e);if(0!==r){if(r<0?(t+=" - ",r=-r):t.length>0&&(t+=" + "),0===e||1!==r){const e=this.field.log(r);0===e?t+="1":1===e?t+="a":(t+="a^",t+=e)}0!==e&&(1===e?t+="x":(t+="x^",t+=e))}}return t}}class Q extends o{}Q.kind="ArithmeticException";class K extends j{constructor(t,e,r){super(),this.primitive=t,this.size=e,this.generatorBase=r;const n=new Int32Array(e);let i=1;for(let r=0;r=e&&(i^=t,i&=e-1);this.expTable=n;const o=new Int32Array(e);for(let t=0;t=(r/2|0);){let t=i,e=s;if(i=o,s=a,i.isZero())throw new q("r_{i-1} was zero");o=t;let r=n.getZero();const l=i.getCoefficient(i.getDegree()),c=n.inverse(l);for(;o.getDegree()>=i.getDegree()&&!o.isZero();){const t=o.getDegree()-i.getDegree(),e=n.multiply(o.getCoefficient(o.getDegree()),c);r=r.addOrSubtract(n.buildMonomial(t,e)),o=o.addOrSubtract(i.multiplyByMonomial(t,e))}if(a=r.multiply(s).addOrSubtract(e),o.getDegree()>=i.getDegree())throw new J("Division algorithm failed to reduce polynomial?")}const l=a.getCoefficient(0);if(0===l)throw new q("sigmaTilde(0) was zero");const c=n.inverse(l);return[a.multiplyScalar(c),o.multiplyScalar(c)]}findErrorLocations(t){const e=t.getDegree();if(1===e)return Int32Array.from([t.getCoefficient(1)]);const r=new Int32Array(e);let n=0;const i=this.field;for(let o=1;o1,h,h+r-1),h+=r-1;else for(let t=r-1;t>=0;--t)c[h++]=0!=(e&1<=8?tt.readCode(t,e,8):tt.readCode(t,e,r)<<8-r}static convertBoolArrayToByteArray(t){let e=new Uint8Array((t.length+7)/8);for(let r=0;r","?","[","]","{","}","CTRL_UL"],tt.DIGIT_TABLE=["CTRL_PS"," ","0","1","2","3","4","5","6","7","8","9",",",".","CTRL_UL","CTRL_US"];class et{constructor(){}static round(t){return NaN===t?0:t<=Number.MIN_SAFE_INTEGER?Number.MIN_SAFE_INTEGER:t>=Number.MAX_SAFE_INTEGER?Number.MAX_SAFE_INTEGER:t+(t<0?-.5:.5)|0}static distance(t,e,r,n){const i=t-r,o=e-n;return Math.sqrt(i*i+o*o)}static sum(t){let e=0;for(let r=0,n=t.length;r!==n;r++)e+=t[r];return e}}class rt{static floatToIntBits(t){return t}}rt.MAX_VALUE=Number.MAX_SAFE_INTEGER;class nt{constructor(t,e){this.x=t,this.y=e}getX(){return this.x}getY(){return this.y}equals(t){if(t instanceof nt){const e=t;return this.x===e.x&&this.y===e.y}return!1}hashCode(){return 31*rt.floatToIntBits(this.x)+rt.floatToIntBits(this.y)}toString(){return"("+this.x+","+this.y+")"}static orderBestPatterns(t){const e=this.distance(t[0],t[1]),r=this.distance(t[1],t[2]),n=this.distance(t[0],t[2]);let i,o,s;if(r>=e&&r>=n?(o=t[0],i=t[1],s=t[2]):n>=r&&n>=e?(o=t[1],i=t[0],s=t[2]):(o=t[2],i=t[0],s=t[1]),this.crossProductZ(i,o,s)<0){const t=i;i=s,s=t}t[0]=i,t[1]=o,t[2]=s}static distance(t,e){return et.distance(t.x,t.y,e.x,e.y)}static crossProductZ(t,e,r){const n=e.x,i=e.y;return(r.x-n)*(t.y-i)-(r.y-i)*(t.x-n)}}class it{constructor(t,e){this.bits=t,this.points=e}getBits(){return this.bits}getPoints(){return this.points}}class ot extends it{constructor(t,e,r,n,i){super(t,e),this.compact=r,this.nbDatablocks=n,this.nbLayers=i}getNbLayers(){return this.nbLayers}getNbDatablocks(){return this.nbDatablocks}isCompact(){return this.compact}}class st{constructor(t,e,r,n){this.image=t,this.height=t.getHeight(),this.width=t.getWidth(),null==e&&(e=st.INIT_SIZE),null==r&&(r=t.getWidth()/2|0),null==n&&(n=t.getHeight()/2|0);const i=e/2|0;if(this.leftInit=r-i,this.rightInit=r+i,this.upInit=n-i,this.downInit=n+i,this.upInit<0||this.leftInit<0||this.downInit>=this.height||this.rightInit>=this.width)throw new y}detect(){let t=this.leftInit,e=this.rightInit,r=this.upInit,n=this.downInit,i=!1,o=!0,s=!1,a=!1,l=!1,c=!1,h=!1;const u=this.width,d=this.height;for(;o;){o=!1;let g=!0;for(;(g||!a)&&e=u){i=!0;break}let f=!0;for(;(f||!l)&&n=d){i=!0;break}let w=!0;for(;(w||!c)&&t>=0;)w=this.containsBlackPoint(r,n,t,!1),w?(t--,o=!0,c=!0):c||t--;if(t<0){i=!0;break}let A=!0;for(;(A||!h)&&r>=0;)A=this.containsBlackPoint(t,e,r,!0),A?(r--,o=!0,h=!0):h||r--;if(r<0){i=!0;break}o&&(s=!0)}if(!i&&s){const i=e-t;let o=null;for(let e=1;null===o&&er||s<-1||s>n)throw new y;i=!1,-1===o?(e[t]=0,i=!0):o===r&&(e[t]=r-1,i=!0),-1===s?(e[t+1]=0,i=!0):s===n&&(e[t+1]=n-1,i=!0)}i=!0;for(let t=e.length-2;t>=0&&i;t-=2){const o=Math.floor(e[t]),s=Math.floor(e[t+1]);if(o<-1||o>r||s<-1||s>n)throw new y;i=!1,-1===o?(e[t]=0,i=!0):o===r&&(e[t]=r-1,i=!0),-1===s?(e[t+1]=0,i=!0):s===n&&(e[t+1]=n-1,i=!0)}}}class lt{constructor(t,e,r,n,i,o,s,a,l){this.a11=t,this.a21=e,this.a31=r,this.a12=n,this.a22=i,this.a32=o,this.a13=s,this.a23=a,this.a33=l}static quadrilateralToQuadrilateral(t,e,r,n,i,o,s,a,l,c,h,u,d,g,f,w){const A=lt.quadrilateralToSquare(t,e,r,n,i,o,s,a);return lt.squareToQuadrilateral(l,c,h,u,d,g,f,w).times(A)}transformPoints(t){const e=t.length,r=this.a11,n=this.a12,i=this.a13,o=this.a21,s=this.a22,a=this.a23,l=this.a31,c=this.a32,h=this.a33;for(let u=0;u>1&127):(n<<=10,n+=(e>>2&992)+(e>>1&31))}let i=this.getCorrectedParameterData(n,this.compact);this.compact?(this.nbLayers=1+(i>>6),this.nbDataBlocks=1+(63&i)):(this.nbLayers=1+(i>>11),this.nbDataBlocks=1+(2047&i))}getRotation(t,e){let r=0;t.forEach(((t,n,i)=>{r=(t>>e-2<<1)+(1&t)+(r<<3)})),r=((1&r)<<11)+(r>>1);for(let t=0;t<4;t++)if(w.bitCount(r^this.EXPECTED_CORNER_BITS[t])<=2)return t;throw new y}getCorrectedParameterData(t,e){let r,n;e?(r=7,n=2):(r=10,n=4);let i=r-n,o=new Int32Array(r);for(let e=r-1;e>=0;--e)o[e]=15&t,t>>=4;try{new $(K.AZTEC_PARAM).decode(o,i)}catch(t){throw new y}let s=0;for(let t=0;t2){let r=this.distancePoint(l,t)*this.nbCenterLayers/(this.distancePoint(i,e)*(this.nbCenterLayers+2));if(r<.75||r>1.25||!this.isWhiteOrBlackRectangle(t,s,a,l))break}e=t,r=s,n=a,i=l,o=!o}if(5!==this.nbCenterLayers&&7!==this.nbCenterLayers)throw new y;this.compact=5===this.nbCenterLayers;let s=new nt(e.getX()+.5,e.getY()-.5),a=new nt(r.getX()+.5,r.getY()+.5),l=new nt(n.getX()-.5,n.getY()+.5),c=new nt(i.getX()-.5,i.getY()-.5);return this.expandSquare([s,a,l,c],2*this.nbCenterLayers-3,2*this.nbCenterLayers)}getMatrixCenter(){let t,e,r,n;try{let i=new st(this.image).detect();t=i[0],e=i[1],r=i[2],n=i[3]}catch(i){let o=this.image.getWidth()/2,s=this.image.getHeight()/2;t=this.getFirstDifferent(new ut(o+7,s-7),!1,1,-1).toResultPoint(),e=this.getFirstDifferent(new ut(o+7,s+7),!1,1,1).toResultPoint(),r=this.getFirstDifferent(new ut(o-7,s+7),!1,-1,1).toResultPoint(),n=this.getFirstDifferent(new ut(o-7,s-7),!1,-1,-1).toResultPoint()}let i=et.round((t.getX()+n.getX()+e.getX()+r.getX())/4),o=et.round((t.getY()+n.getY()+e.getY()+r.getY())/4);try{let s=new st(this.image,15,i,o).detect();t=s[0],e=s[1],r=s[2],n=s[3]}catch(s){t=this.getFirstDifferent(new ut(i+7,o-7),!1,1,-1).toResultPoint(),e=this.getFirstDifferent(new ut(i+7,o+7),!1,1,1).toResultPoint(),r=this.getFirstDifferent(new ut(i-7,o+7),!1,-1,1).toResultPoint(),n=this.getFirstDifferent(new ut(i-7,o-7),!1,-1,-1).toResultPoint()}return i=et.round((t.getX()+n.getX()+e.getX()+r.getX())/4),o=et.round((t.getY()+n.getY()+e.getY()+r.getY())/4),new ut(i,o)}getMatrixCornerPoints(t){return this.expandSquare(t,2*this.nbCenterLayers,this.getDimension())}sampleGrid(t,e,r,n,i){let o=ht.getInstance(),s=this.getDimension(),a=s/2-this.nbCenterLayers,l=s/2+this.nbCenterLayers;return o.sampleGrid(t,s,s,a,a,l,a,l,l,a,l,e.getX(),e.getY(),r.getX(),r.getY(),n.getX(),n.getY(),i.getX(),i.getY())}sampleLine(t,e,r){let n=0,i=this.distanceResultPoint(t,e),o=i/r,s=t.getX(),a=t.getY(),l=o*(e.getX()-t.getX())/i,c=o*(e.getY()-t.getY())/i;for(let t=0;t.1&&h<.9?0:h<=.1===l?1:-1}getFirstDifferent(t,e,r,n){let i=t.getX()+r,o=t.getY()+n;for(;this.isValid(i,o)&&this.image.get(i,o)===e;)i+=r,o+=n;for(i-=r,o-=n;this.isValid(i,o)&&this.image.get(i,o)===e;)i+=r;for(i-=r;this.isValid(i,o)&&this.image.get(i,o)===e;)o+=n;return o-=n,new ut(i,o)}expandSquare(t,e,r){let n=r/(2*e),i=t[0].getX()-t[2].getX(),o=t[0].getY()-t[2].getY(),s=(t[0].getX()+t[2].getX())/2,a=(t[0].getY()+t[2].getY())/2,l=new nt(s+n*i,a+n*o),c=new nt(s-n*i,a-n*o);return i=t[1].getX()-t[3].getX(),o=t[1].getY()-t[3].getY(),s=(t[1].getX()+t[3].getX())/2,a=(t[1].getY()+t[3].getY())/2,[l,new nt(s+n*i,a+n*o),c,new nt(s-n*i,a-n*o)]}isValid(t,e){return t>=0&&t0&&e{r.foundPossibleResultPoint(t)}))}}reset(){}}class ft{decode(t,e){try{return this.doDecode(t,e)}catch(r){if(e&&!0===e.get(E.TRY_HARDER)&&t.isRotateSupported()){const r=t.rotateCounterClockwise(),n=this.doDecode(r,e),i=n.getResultMetadata();let o=270;null!==i&&!0===i.get(X.ORIENTATION)&&(o+=i.get(X.ORIENTATION)%360),n.putMetadata(X.ORIENTATION,o);const s=n.getResultPoints();if(null!==s){const t=r.getHeight();for(let e=0;e>(o?8:5));let a;a=o?n:15;const l=Math.trunc(n/2);for(let o=0;o=n)break;try{i=t.getBlackRow(c,i)}catch(t){continue}for(let t=0;t<2;t++){if(1===t&&(i.reverse(),e&&!0===e.get(E.NEED_RESULT_POINT_CALLBACK))){const t=new Map;e.forEach(((e,r)=>t.set(r,e))),t.delete(E.NEED_RESULT_POINT_CALLBACK),e=t}try{const n=this.decodeRow(c,i,e);if(1===t){n.putMetadata(X.ORIENTATION,180);const t=n.getResultPoints();null!==t&&(t[0]=new nt(r-t[0].getX()-1,t[0].getY()),t[1]=new nt(r-t[1].getX()-1,t[1].getY()))}return n}catch(t){}}}throw new y}static recordPattern(t,e,r){const n=r.length;for(let t=0;t=i)throw new y;let o=!t.get(e),s=0,a=e;for(;a0&&n>=0;)t.get(--e)!==i&&(n--,i=!i);if(n>=0)throw new y;ft.recordPattern(t,e+1,r)}static patternMatchVariance(t,e,r){const n=t.length;let i=0,o=0;for(let r=0;ro?n-o:o-n;if(l>r)return Number.POSITIVE_INFINITY;a+=l}return a/i}}class wt extends ft{static findStartPattern(t){const e=t.getSize(),r=t.getNextSet(0);let n=0,i=Int32Array.from([0,0,0,0,0,0]),o=r,s=!1;for(let a=r;a=0&&t.isRange(Math.max(0,o-(a-o)/2),o,!1))return Int32Array.from([o,a,r]);o+=i[0]+i[1],i=i.slice(2,i.length-1),i[n-1]=0,i[n]=0,n--}else n++;i[n]=1,s=!s}throw new y}static decodeCode(t,e,r){ft.recordPattern(t,r,e);let n=wt.MAX_AVG_VARIANCE,i=-1;for(let t=0;t=0)return i;throw new y}decodeRow(t,e,r){const n=r&&!0===r.get(E.ASSUME_GS1),i=wt.findStartPattern(e),o=i[2];let s=0;const a=new Uint8Array(20);let l;switch(a[s++]=o,o){case wt.CODE_START_A:l=wt.CODE_CODE_A;break;case wt.CODE_START_B:l=wt.CODE_CODE_B;break;case wt.CODE_START_C:l=wt.CODE_CODE_C;break;default:throw new C}let h=!1,u=!1,d="",g=i[0],f=i[1];const w=Int32Array.from([0,0,0,0,0,0]);let A=0,m=0,I=o,_=0,p=!0,S=!1,T=!1;for(;!h;){const t=u;switch(u=!1,A=m,m=wt.decodeCode(e,w,f),a[s++]=m,m!==wt.CODE_STOP&&(p=!0),m!==wt.CODE_STOP&&(_++,I+=_*m),g=f,f+=w.reduce(((t,e)=>t+e),0),m){case wt.CODE_START_A:case wt.CODE_START_B:case wt.CODE_START_C:throw new C}switch(l){case wt.CODE_CODE_A:if(m<64)d+=T===S?String.fromCharCode(" ".charCodeAt(0)+m):String.fromCharCode(" ".charCodeAt(0)+m+128),T=!1;else if(m<96)d+=T===S?String.fromCharCode(m-64):String.fromCharCode(m+64),T=!1;else switch(m!==wt.CODE_STOP&&(p=!1),m){case wt.CODE_FNC_1:n&&(0===d.length?d+="]C1":d+=String.fromCharCode(29));break;case wt.CODE_FNC_2:case wt.CODE_FNC_3:break;case wt.CODE_FNC_4_A:!S&&T?(S=!0,T=!1):S&&T?(S=!1,T=!1):T=!0;break;case wt.CODE_SHIFT:u=!0,l=wt.CODE_CODE_B;break;case wt.CODE_CODE_B:l=wt.CODE_CODE_B;break;case wt.CODE_CODE_C:l=wt.CODE_CODE_C;break;case wt.CODE_STOP:h=!0}break;case wt.CODE_CODE_B:if(m<96)d+=T===S?String.fromCharCode(" ".charCodeAt(0)+m):String.fromCharCode(" ".charCodeAt(0)+m+128),T=!1;else switch(m!==wt.CODE_STOP&&(p=!1),m){case wt.CODE_FNC_1:n&&(0===d.length?d+="]C1":d+=String.fromCharCode(29));break;case wt.CODE_FNC_2:case wt.CODE_FNC_3:break;case wt.CODE_FNC_4_B:!S&&T?(S=!0,T=!1):S&&T?(S=!1,T=!1):T=!0;break;case wt.CODE_SHIFT:u=!0,l=wt.CODE_CODE_A;break;case wt.CODE_CODE_A:l=wt.CODE_CODE_A;break;case wt.CODE_CODE_C:l=wt.CODE_CODE_C;break;case wt.CODE_STOP:h=!0}break;case wt.CODE_CODE_C:if(m<100)m<10&&(d+="0"),d+=m;else switch(m!==wt.CODE_STOP&&(p=!1),m){case wt.CODE_FNC_1:n&&(0===d.length?d+="]C1":d+=String.fromCharCode(29));break;case wt.CODE_CODE_A:l=wt.CODE_CODE_A;break;case wt.CODE_CODE_B:l=wt.CODE_CODE_B;break;case wt.CODE_STOP:h=!0}}t&&(l=l===wt.CODE_CODE_A?wt.CODE_CODE_B:wt.CODE_CODE_A)}const N=f-g;if(f=e.getNextUnset(f),!e.isRange(f,Math.min(e.getSize(),f+(f-g)/2),!1))throw new y;if(I-=_*A,I%103!==A)throw new c;const M=d.length;if(0===M)throw new y;M>0&&p&&(d=l===wt.CODE_CODE_C?d.substring(0,M-2):d.substring(0,M-1));const D=(i[1]+i[0])/2,R=g+N/2,O=a.length,b=new Uint8Array(O);for(let t=0;tn&&(i=e);n=i,e=0;let o=0,s=0;for(let i=0;in&&(s|=1<0;i++){let r=t[i];if(r>n&&(e--,2*r>=o))return-1}return s}}while(e>3);return-1}static patternToChar(t){for(let e=0;e="A"&&i<="Z"))throw new C;o=String.fromCharCode(i.charCodeAt(0)+32);break;case"$":if(!(i>="A"&&i<="Z"))throw new C;o=String.fromCharCode(i.charCodeAt(0)-64);break;case"%":if(i>="A"&&i<="E")o=String.fromCharCode(i.charCodeAt(0)-38);else if(i>="F"&&i<="J")o=String.fromCharCode(i.charCodeAt(0)-11);else if(i>="K"&&i<="O")o=String.fromCharCode(i.charCodeAt(0)+16);else if(i>="P"&&i<="T")o=String.fromCharCode(i.charCodeAt(0)+43);else if("U"===i)o="\0";else if("V"===i)o="@";else if("W"===i)o="`";else{if("X"!==i&&"Y"!==i&&"Z"!==i)throw new C;o=""}break;case"/":if(i>="A"&&i<="O")o=String.fromCharCode(i.charCodeAt(0)-32);else{if("Z"!==i)throw new C;o=":"}}r+=o,n++}else r+=e}return r}}At.ALPHABET_STRING="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%",At.CHARACTER_ENCODINGS=[52,289,97,352,49,304,112,37,292,100,265,73,328,25,280,88,13,268,76,28,259,67,322,19,274,82,7,262,70,22,385,193,448,145,400,208,133,388,196,168,162,138,42],At.ASTERISK_ENCODING=148;class mt extends ft{constructor(){super(...arguments),this.narrowLineWidth=-1}decodeRow(t,e,r){let n=this.decodeStart(e),i=this.decodeEnd(e),o=new T;mt.decodeMiddle(e,n[1],i[0],o);let s=o.toString(),a=null;null!=r&&(a=r.get(E.ALLOWED_LENGTHS)),null==a&&(a=mt.DEFAULT_ALLOWED_LENGTHS);let l=s.length,c=!1,h=0;for(let t of a){if(l===t){c=!0;break}t>h&&(h=t)}if(!c&&l>h&&(c=!0),!c)throw new C;const u=[new nt(n[1],t),new nt(i[0],t)];return new F(s,null,0,u,k.ITF,(new Date).getTime())}static decodeMiddle(t,e,r,n){let i=new Int32Array(10),o=new Int32Array(5),s=new Int32Array(5);for(i.fill(0),o.fill(0),s.fill(0);e0&&n>=0&&!t.get(n);n--)r--;if(0!==r)throw new y}static skipWhiteSpace(t){const e=t.getSize(),r=t.getNextSet(0);if(r===e)throw new y;return r}decodeEnd(t){t.reverse();try{let e,r=mt.skipWhiteSpace(t);try{e=mt.findGuardPattern(t,r,mt.END_PATTERN_REVERSED[0])}catch(n){n instanceof y&&(e=mt.findGuardPattern(t,r,mt.END_PATTERN_REVERSED[1]))}this.validateQuietZone(t,e[0]);let n=e[0];return e[0]=t.getSize()-e[1],e[1]=t.getSize()-n,e}finally{t.reverse()}}static findGuardPattern(t,e,r){let n=r.length,i=new Int32Array(n),o=t.getSize(),s=!1,a=0,l=e;i.fill(0);for(let c=e;c=0)return r%10;throw new y}}mt.PATTERNS=[Int32Array.from([1,1,2,2,1]),Int32Array.from([2,1,1,1,2]),Int32Array.from([1,2,1,1,2]),Int32Array.from([2,2,1,1,1]),Int32Array.from([1,1,2,1,2]),Int32Array.from([2,1,2,1,1]),Int32Array.from([1,2,2,1,1]),Int32Array.from([1,1,1,2,2]),Int32Array.from([2,1,1,2,1]),Int32Array.from([1,2,1,2,1]),Int32Array.from([1,1,3,3,1]),Int32Array.from([3,1,1,1,3]),Int32Array.from([1,3,1,1,3]),Int32Array.from([3,3,1,1,1]),Int32Array.from([1,1,3,1,3]),Int32Array.from([3,1,3,1,1]),Int32Array.from([1,3,3,1,1]),Int32Array.from([1,1,1,3,3]),Int32Array.from([3,1,1,3,1]),Int32Array.from([1,3,1,3,1])],mt.MAX_AVG_VARIANCE=.38,mt.MAX_INDIVIDUAL_VARIANCE=.5,mt.DEFAULT_ALLOWED_LENGTHS=[6,8,10,12,14],mt.START_PATTERN=Int32Array.from([1,1,1,1]),mt.END_PATTERN_REVERSED=[Int32Array.from([1,1,2]),Int32Array.from([1,1,3])];class Et extends ft{constructor(){super(...arguments),this.decodeRowStringBuffer=""}static findStartGuardPattern(t){let e,r=!1,n=0,i=Int32Array.from([0,0,0]);for(;!r;){i=Int32Array.from([0,0,0]),e=Et.findGuardPattern(t,n,!1,this.START_END_PATTERN,i);let o=e[0];n=e[1];let s=o-(n-o);s>=0&&(r=t.isRange(s,o,!1))}return e}static checkChecksum(t){return Et.checkStandardUPCEANChecksum(t)}static checkStandardUPCEANChecksum(t){let e=t.length;if(0===e)return!1;let r=parseInt(t.charAt(e-1),10);return Et.getStandardUPCEANChecksum(t.substring(0,e-1))===r}static getStandardUPCEANChecksum(t){let e=t.length,r=0;for(let n=e-1;n>=0;n-=2){let e=t.charAt(n).charCodeAt(0)-"0".charCodeAt(0);if(e<0||e>9)throw new C;r+=e}r*=3;for(let n=e-2;n>=0;n-=2){let e=t.charAt(n).charCodeAt(0)-"0".charCodeAt(0);if(e<0||e>9)throw new C;r+=e}return(1e3-r)%10}static decodeEnd(t,e){return Et.findGuardPattern(t,e,!1,Et.START_END_PATTERN,new Int32Array(Et.START_END_PATTERN.length).fill(0))}static findGuardPatternWithoutCounters(t,e,r,n){return this.findGuardPattern(t,e,r,n,new Int32Array(n.length))}static findGuardPattern(t,e,r,n,i){let o=t.getSize(),s=0,a=e=r?t.getNextUnset(e):t.getNextSet(e),l=n.length,c=r;for(let r=e;r=0)return o;throw new y}}Et.MAX_AVG_VARIANCE=.48,Et.MAX_INDIVIDUAL_VARIANCE=.7,Et.START_END_PATTERN=Int32Array.from([1,1,1]),Et.MIDDLE_PATTERN=Int32Array.from([1,1,1,1,1]),Et.END_PATTERN=Int32Array.from([1,1,1,1,1,1]),Et.L_PATTERNS=[Int32Array.from([3,2,1,1]),Int32Array.from([2,2,2,1]),Int32Array.from([2,1,2,2]),Int32Array.from([1,4,1,1]),Int32Array.from([1,1,3,2]),Int32Array.from([1,2,3,1]),Int32Array.from([1,1,1,4]),Int32Array.from([1,3,1,2]),Int32Array.from([1,2,1,3]),Int32Array.from([3,1,1,2])];class Ct{constructor(){this.CHECK_DIGIT_ENCODINGS=[24,20,18,17,12,6,3,10,9,5],this.decodeMiddleCounters=Int32Array.from([0,0,0,0]),this.decodeRowStringBuffer=""}decodeRow(t,e,r){let n=this.decodeRowStringBuffer,i=this.decodeMiddle(e,r,n),o=n.toString(),s=Ct.parseExtensionString(o),a=[new nt((r[0]+r[1])/2,t),new nt(i,t)],l=new F(o,null,0,a,k.UPC_EAN_EXTENSION,(new Date).getTime());return null!=s&&l.putAllMetadata(s),l}decodeMiddle(t,e,r){let n=this.decodeMiddleCounters;n[0]=0,n[1]=0,n[2]=0,n[3]=0;let i=t.getSize(),o=e[1],s=0;for(let e=0;e<5&&o=10&&(s|=1<<4-e),4!==e&&(o=t.getNextSet(o),o=t.getNextUnset(o))}if(5!==r.length)throw new y;let a=this.determineCheckDigit(s);if(Ct.extensionChecksum(r.toString())!==a)throw new y;return o}static extensionChecksum(t){let e=t.length,r=0;for(let n=e-2;n>=0;n-=2)r+=t.charAt(n).charCodeAt(0)-"0".charCodeAt(0);r*=3;for(let n=e-1;n>=0;n-=2)r+=t.charAt(n).charCodeAt(0)-"0".charCodeAt(0);return r*=3,r%10}determineCheckDigit(t){for(let e=0;e<10;e++)if(t===this.CHECK_DIGIT_ENCODINGS[e])return e;throw new y}static parseExtensionString(t){if(5!==t.length)return null;let e=Ct.parseExtension5String(t);return null==e?null:new Map([[X.SUGGESTED_PRICE,e]])}static parseExtension5String(t){let e;switch(t.charAt(0)){case"0":e="£";break;case"5":e="$";break;case"9":switch(t){case"90000":return null;case"99991":return"0.00";case"99990":return"Used"}e="";break;default:e=""}let r=parseInt(t.substring(1)),n=r%100;return e+(r/100).toString()+"."+(n<10?"0"+n:n.toString())}}class It{constructor(){this.decodeMiddleCounters=Int32Array.from([0,0,0,0]),this.decodeRowStringBuffer=""}decodeRow(t,e,r){let n=this.decodeRowStringBuffer,i=this.decodeMiddle(e,r,n),o=n.toString(),s=It.parseExtensionString(o),a=[new nt((r[0]+r[1])/2,t),new nt(i,t)],l=new F(o,null,0,a,k.UPC_EAN_EXTENSION,(new Date).getTime());return null!=s&&l.putAllMetadata(s),l}decodeMiddle(t,e,r){let n=this.decodeMiddleCounters;n[0]=0,n[1]=0,n[2]=0,n[3]=0;let i=t.getSize(),o=e[1],s=0;for(let e=0;e<2&&o=10&&(s|=1<<1-e),1!==e&&(o=t.getNextSet(o),o=t.getNextUnset(o))}if(2!==r.length)throw new y;if(parseInt(r.toString())%4!==s)throw new y;return o}static parseExtensionString(t){return 2!==t.length?null:new Map([[X.ISSUE_NUMBER,parseInt(t)]])}}class _t{static decodeRow(t,e,r){let n=Et.findGuardPattern(e,r,!1,this.EXTENSION_START_PATTERN,new Int32Array(this.EXTENSION_START_PATTERN.length).fill(0));try{return(new Ct).decodeRow(t,e,n)}catch(r){return(new It).decodeRow(t,e,n)}}}_t.EXTENSION_START_PATTERN=Int32Array.from([1,1,2]);class pt extends Et{constructor(){super(),this.decodeRowStringBuffer="",pt.L_AND_G_PATTERNS=pt.L_PATTERNS.map((t=>Int32Array.from(t)));for(let t=10;t<20;t++){let e=pt.L_PATTERNS[t-10],r=new Int32Array(e.length);for(let t=0;t=e.getSize()||!e.isRange(h,u,!1))throw new y;let d=a.toString();if(d.length<8)throw new C;if(!pt.checkChecksum(d))throw new c;let g=(n[1]+n[0])/2,f=(l[1]+l[0])/2,w=this.getBarcodeFormat(),A=[new nt(g,t),new nt(f,t)],m=new F(d,null,0,A,w,(new Date).getTime()),I=0;try{let r=_t.decodeRow(t,e,l[1]);m.putMetadata(X.UPC_EAN_EXTENSION,r.getText()),m.putAllMetadata(r.getResultMetadata()),m.addResultPoints(r.getResultPoints()),I=r.getText().length}catch(t){}let _=null==r?null:r.get(E.ALLOWED_EAN_EXTENSIONS);if(null!=_){let t=!1;for(let e in _)if(I.toString()===e){t=!0;break}if(!t)throw new y}return w===k.EAN_13||k.UPC_A,m}static checkChecksum(t){return pt.checkStandardUPCEANChecksum(t)}static checkStandardUPCEANChecksum(t){let e=t.length;if(0===e)return!1;let r=parseInt(t.charAt(e-1),10);return pt.getStandardUPCEANChecksum(t.substring(0,e-1))===r}static getStandardUPCEANChecksum(t){let e=t.length,r=0;for(let n=e-1;n>=0;n-=2){let e=t.charAt(n).charCodeAt(0)-"0".charCodeAt(0);if(e<0||e>9)throw new C;r+=e}r*=3;for(let n=e-2;n>=0;n-=2){let e=t.charAt(n).charCodeAt(0)-"0".charCodeAt(0);if(e<0||e>9)throw new C;r+=e}return(1e3-r)%10}static decodeEnd(t,e){return pt.findGuardPattern(t,e,!1,pt.START_END_PATTERN,new Int32Array(pt.START_END_PATTERN.length).fill(0))}}class St extends pt{constructor(){super(),this.decodeMiddleCounters=Int32Array.from([0,0,0,0])}decodeMiddle(t,e,r){let n=this.decodeMiddleCounters;n[0]=0,n[1]=0,n[2]=0,n[3]=0;let i=t.getSize(),o=e[1],s=0;for(let e=0;e<6&&o=10&&(s|=1<<5-e)}r=St.determineFirstDigit(r,s),o=pt.findGuardPattern(t,o,!0,pt.MIDDLE_PATTERN,new Int32Array(pt.MIDDLE_PATTERN.length).fill(0))[1];for(let e=0;e<6&&ot));n[0]=0,n[1]=0,n[2]=0,n[3]=0;const i=t.getSize();let o=e[1],s=0;for(let e=0;e<6&&o=10&&(s|=1<<5-e)}return yt.determineNumSysAndCheckDigit(new T(r),s),o}decodeEnd(t,e){return yt.findGuardPatternWithoutCounters(t,e,!0,yt.MIDDLE_END_PATTERN)}checkChecksum(t){return pt.checkChecksum(yt.convertUPCEtoUPCA(t))}static determineNumSysAndCheckDigit(t,e){for(let r=0;r<=1;r++)for(let n=0;n<10;n++)if(e===this.NUMSYS_AND_CHECK_DIGIT_PATTERNS[r][n])return t.insert(0,"0"+r),void t.append("0"+n);throw y.getNotFoundInstance()}getBarcodeFormat(){return k.UPC_E}static convertUPCEtoUPCA(t){const e=t.slice(1,7).split("").map((t=>t.charCodeAt(0))),r=new T;r.append(t.charAt(0));let n=e[5];switch(n){case 0:case 1:case 2:r.appendChars(e,0,2),r.append(n),r.append("0000"),r.appendChars(e,2,3);break;case 3:r.appendChars(e,0,3),r.append("00000"),r.appendChars(e,3,2);break;case 4:r.appendChars(e,0,4),r.append("00000"),r.append(e[4]);break;default:r.appendChars(e,0,5),r.append("0000"),r.append(n)}return t.length>=8&&r.append(t.charAt(7)),r.toString()}}yt.MIDDLE_END_PATTERN=Int32Array.from([1,1,1,1,1,1]),yt.NUMSYS_AND_CHECK_DIGIT_PATTERNS=[Int32Array.from([56,52,50,49,44,38,35,42,41,37]),Int32Array.from([7,11,13,14,19,25,28,21,22,1])];class Mt extends ft{constructor(t){super();let e=null==t?null:t.get(E.POSSIBLE_FORMATS),r=[];null!=e&&(e.indexOf(k.EAN_13)>-1?r.push(new St):e.indexOf(k.UPC_A)>-1&&r.push(new Nt),e.indexOf(k.EAN_8)>-1&&r.push(new Tt),e.indexOf(k.UPC_E)>-1&&r.push(new yt)),0===r.length&&(r.push(new St),r.push(new Tt),r.push(new yt)),this.readers=r}decodeRow(t,e,r){for(let n of this.readers)try{const i=n.decodeRow(t,e,r),o=i.getBarcodeFormat()===k.EAN_13&&"0"===i.getText().charAt(0),s=null==r?null:r.get(E.POSSIBLE_FORMATS),a=null==s||s.includes(k.UPC_A);if(o&&a){const t=i.getRawBytes(),e=new F(i.getText().substring(1),t,t.length,i.getResultPoints(),k.UPC_A);return e.putAllMetadata(i.getResultMetadata()),e}return i}catch(t){}throw new y}reset(){for(let t of this.readers)t.reset()}}class Dt extends ft{constructor(){super(),this.decodeFinderCounters=new Int32Array(4),this.dataCharacterCounters=new Int32Array(8),this.oddRoundingErrors=new Array(4),this.evenRoundingErrors=new Array(4),this.oddCounts=new Array(this.dataCharacterCounters.length/2),this.evenCounts=new Array(this.dataCharacterCounters.length/2)}getDecodeFinderCounters(){return this.decodeFinderCounters}getDataCharacterCounters(){return this.dataCharacterCounters}getOddRoundingErrors(){return this.oddRoundingErrors}getEvenRoundingErrors(){return this.evenRoundingErrors}getOddCounts(){return this.oddCounts}getEvenCounts(){return this.evenCounts}parseFinderValue(t,e){for(let r=0;rn&&(n=e[i],r=i);t[r]++}static decrement(t,e){let r=0,n=e[0];for(let i=1;i=Dt.MIN_FINDER_PATTERN_RATIO&&r<=Dt.MAX_FINDER_PATTERN_RATIO){let e=Number.MAX_SAFE_INTEGER,r=Number.MIN_SAFE_INTEGER;for(let n of t)n>r&&(r=n),n=s-a-1&&(t-=bt.combins(n-l-(s-a),s-a-2)),s-a-1>1){let r=0;for(let t=n-l-(s-a-2);t>e;t--)r+=bt.combins(n-l-t-1,s-a-3);t-=r*(s-1-a)}else n-l>e&&t--;i+=t}n-=l}return i}static combins(t,e){let r,n;t-e>e?(n=e,r=t-e):(n=t-e,r=e);let i=1,o=1;for(let e=t;e>r;e--)i*=e,o<=n&&(i/=o,o++);for(;o<=n;)i/=o,o++;return i}}class Lt{constructor(t,e){e?this.decodedInformation=null:(this.finished=t,this.decodedInformation=e)}getDecodedInformation(){return this.decodedInformation}isFinished(){return this.finished}}class Bt{constructor(t){this.newPosition=t}getNewPosition(){return this.newPosition}}class Pt extends Bt{constructor(t,e){super(t),this.value=e}getValue(){return this.value}isFNC1(){return this.value===Pt.FNC1}}Pt.FNC1="$";class vt extends Bt{constructor(t,e,r){super(t),r?(this.remaining=!0,this.remainingValue=this.remainingValue):(this.remaining=!1,this.remainingValue=0),this.newString=e}getNewString(){return this.newString}isRemaining(){return this.remaining}getRemainingValue(){return this.remainingValue}}class Ft extends Bt{constructor(t,e,r){if(super(t),e<0||e>10||r<0||r>10)throw new C;this.firstDigit=e,this.secondDigit=r}getFirstDigit(){return this.firstDigit}getSecondDigit(){return this.secondDigit}getValue(){return 10*this.firstDigit+this.secondDigit}isFirstDigitFNC1(){return this.firstDigit===Ft.FNC1}isSecondDigitFNC1(){return this.secondDigit===Ft.FNC1}isAnyFNC1(){return this.firstDigit===Ft.FNC1||this.secondDigit===Ft.FNC1}}Ft.FNC1=10;class xt{constructor(){}static parseFieldsInGeneralPurpose(t){if(!t)return null;if(t.length<2)throw new y;let e=t.substring(0,2);for(let r of xt.TWO_DIGIT_DATA_LENGTH)if(r[0]===e)return r[1]===xt.VARIABLE_LENGTH?xt.processVariableAI(2,r[2],t):xt.processFixedAI(2,r[1],t);if(t.length<3)throw new y;let r=t.substring(0,3);for(let e of xt.THREE_DIGIT_DATA_LENGTH)if(e[0]===r)return e[1]===xt.VARIABLE_LENGTH?xt.processVariableAI(3,e[2],t):xt.processFixedAI(3,e[1],t);for(let e of xt.THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH)if(e[0]===r)return e[1]===xt.VARIABLE_LENGTH?xt.processVariableAI(4,e[2],t):xt.processFixedAI(4,e[1],t);if(t.length<4)throw new y;let n=t.substring(0,4);for(let e of xt.FOUR_DIGIT_DATA_LENGTH)if(e[0]===n)return e[1]===xt.VARIABLE_LENGTH?xt.processVariableAI(4,e[2],t):xt.processFixedAI(4,e[1],t);throw new y}static processFixedAI(t,e,r){if(r.lengththis.information.getSize())return t+4<=this.information.getSize();for(let e=t;ethis.information.getSize()){let e=this.extractNumericValueFromBitArray(t,4);return new Ft(this.information.getSize(),0===e?Ft.FNC1:e-1,Ft.FNC1)}let e=this.extractNumericValueFromBitArray(t,7);return new Ft(t+7,(e-8)/11,(e-8)%11)}extractNumericValueFromBitArray(t,e){return kt.extractNumericValueFromBitArray(this.information,t,e)}static extractNumericValueFromBitArray(t,e,r){let n=0;for(let i=0;ithis.information.getSize())return!1;let e=this.extractNumericValueFromBitArray(t,5);if(e>=5&&e<16)return!0;if(t+7>this.information.getSize())return!1;let r=this.extractNumericValueFromBitArray(t,7);if(r>=64&&r<116)return!0;if(t+8>this.information.getSize())return!1;let n=this.extractNumericValueFromBitArray(t,8);return n>=232&&n<253}decodeIsoIec646(t){let e=this.extractNumericValueFromBitArray(t,5);if(15===e)return new Pt(t+5,Pt.FNC1);if(e>=5&&e<15)return new Pt(t+5,"0"+(e-5));let r,n=this.extractNumericValueFromBitArray(t,7);if(n>=64&&n<90)return new Pt(t+7,""+(n+1));if(n>=90&&n<116)return new Pt(t+7,""+(n+7));switch(this.extractNumericValueFromBitArray(t,8)){case 232:r="!";break;case 233:r='"';break;case 234:r="%";break;case 235:r="&";break;case 236:r="'";break;case 237:r="(";break;case 238:r=")";break;case 239:r="*";break;case 240:r="+";break;case 241:r=",";break;case 242:r="-";break;case 243:r=".";break;case 244:r="/";break;case 245:r=":";break;case 246:r=";";break;case 247:r="<";break;case 248:r="=";break;case 249:r=">";break;case 250:r="?";break;case 251:r="_";break;case 252:r=" ";break;default:throw new C}return new Pt(t+8,r)}isStillAlpha(t){if(t+5>this.information.getSize())return!1;let e=this.extractNumericValueFromBitArray(t,5);if(e>=5&&e<16)return!0;if(t+6>this.information.getSize())return!1;let r=this.extractNumericValueFromBitArray(t,6);return r>=16&&r<63}decodeAlphanumeric(t){let e=this.extractNumericValueFromBitArray(t,5);if(15===e)return new Pt(t+5,Pt.FNC1);if(e>=5&&e<15)return new Pt(t+5,"0"+(e-5));let r,n=this.extractNumericValueFromBitArray(t,6);if(n>=32&&n<58)return new Pt(t+6,""+(n+33));switch(n){case 58:r="*";break;case 59:r=",";break;case 60:r="-";break;case 61:r=".";break;case 62:r="/";break;default:throw new J("Decoding invalid alphanumeric value: "+n)}return new Pt(t+6,r)}isAlphaTo646ToAlphaLatch(t){if(t+1>this.information.getSize())return!1;for(let e=0;e<5&&e+tthis.information.getSize())return!1;for(let e=t;ethis.information.getSize())return!1;for(let e=0;e<4&&e+t{e.forEach((e=>{t.getLeftChar().getValue()===e.getLeftChar().getValue()&&t.getRightChar().getValue()===e.getRightChar().getValue()&&t.getFinderPatter().getValue()===e.getFinderPatter().getValue()&&(r=!0)}))})),r}}class Jt extends Dt{constructor(t){super(...arguments),this.pairs=new Array(Jt.MAX_PAIRS),this.rows=new Array,this.startEnd=[2],this.verbose=!0===t}decodeRow(t,e,r){this.pairs.length=0,this.startFromEven=!1;try{return Jt.constructResult(this.decodeRow2pairs(t,e))}catch(t){this.verbose&&console.log(t)}return this.pairs.length=0,this.startFromEven=!0,Jt.constructResult(this.decodeRow2pairs(t,e))}reset(){this.pairs.length=0,this.rows.length=0}decodeRow2pairs(t,e){let r,n=!1;for(;!n;)try{this.pairs.push(this.retrieveNextPair(e,this.pairs,t))}catch(t){if(t instanceof y){if(!this.pairs.length)throw new y;n=!0}}if(this.checkChecksum())return this.pairs;if(r=!!this.rows.length,this.storeRow(t,!1),r){let t=this.checkRowsBoolean(!1);if(null!=t)return t;if(t=this.checkRowsBoolean(!0),null!=t)return t}throw new y}checkRowsBoolean(t){if(this.rows.length>25)return this.rows.length=0,null;this.pairs.length=0,t&&(this.rows=this.rows.reverse());let e=null;try{e=this.checkRows(new Array,0)}catch(t){this.verbose&&console.log(t)}return t&&(this.rows=this.rows.reverse()),e}checkRows(t,e){for(let r=e;re.length)continue;let r=!0;for(let n=0;nt){i=e.isEquivalent(this.pairs);break}n=e.isEquivalent(this.pairs),r++}i||n||Jt.isPartialRow(this.pairs,this.rows)||(this.rows.push(r,new qt(this.pairs,t,e)),this.removePartialRows(this.pairs,this.rows))}removePartialRows(t,e){for(let r of e)if(r.getPairs().length!==t.length)for(let e of r.getPairs())for(let r of t)if(Kt.equals(e,r))break}static isPartialRow(t,e){for(let r of e){let e=!0;for(let n of t){let t=!1;for(let e of r.getPairs())if(n.equals(e)){t=!0;break}if(!t){e=!1;break}}if(e)return!0}return!1}getRows(){return this.rows}static constructResult(t){let e=function(t){try{if(t.get(1))return new Vt(t);if(!t.get(2))return new zt(t);switch(kt.extractNumericValueFromBitArray(t,1,4)){case 4:return new Xt(t);case 5:return new Wt(t)}switch(kt.extractNumericValueFromBitArray(t,1,5)){case 12:return new jt(t);case 13:return new Zt(t)}switch(kt.extractNumericValueFromBitArray(t,1,7)){case 56:return new Qt(t,"310","11");case 57:return new Qt(t,"320","11");case 58:return new Qt(t,"310","13");case 59:return new Qt(t,"320","13");case 60:return new Qt(t,"310","15");case 61:return new Qt(t,"320","15");case 62:return new Qt(t,"310","17");case 63:return new Qt(t,"320","17")}}catch(e){throw console.log(e),new J("unknown decoder: "+t)}}(class{static buildBitArray(t){let e=2*t.length-1;null==t[t.length-1].getRightChar()&&(e-=1);let r=new A(12*e),n=0,i=t[0].getRightChar().getValue();for(let t=11;t>=0;--t)0!=(i&1<=0;--t)0!=(o&1<=0;--e)0!=(t&1<=0?r:this.isEmptyPair(e)?0:e[e.length-1].getFinderPattern().getStartEnd()[1];let s=e.length%2!=0;this.startFromEven&&(s=!s);let a=!1;for(;i=0&&!t.get(e);)e--;e++,n=this.startEnd[0]-e,i=e,o=this.startEnd[1]}else i=this.startEnd[0],o=t.getNextUnset(this.startEnd[1]+1),n=o-this.startEnd[1];let s,a=this.getDecodeFinderCounters();u.arraycopy(a,0,a,1,a.length-1),a[0]=n;try{s=this.parseFinderValue(a,Jt.FINDER_PATTERNS)}catch(t){return null}return new Ot(s,[i,o],i,o,e)}decodeDataCharacter(t,e,r,n){let i=this.getDataCharacterCounters();for(let t=0;t.3)throw new y;let a=this.getOddCounts(),l=this.getEvenCounts(),c=this.getOddRoundingErrors(),h=this.getEvenRoundingErrors();for(let t=0;t8){if(e>8.7)throw new y;r=8}let n=t/2;0==(1&t)?(a[n]=r,c[n]=e-r):(l[n]=r,h[n]=e-r)}this.adjustOddEvenCounts(17);let u=4*e.getValue()+(r?0:2)+(n?0:1)-1,d=0,g=0;for(let t=a.length-1;t>=0;t--){if(Jt.isNotA1left(e,r,n)){let e=Jt.WEIGHTS[u][2*t];g+=a[t]*e}d+=a[t]}let f=0;for(let t=l.length-1;t>=0;t--)if(Jt.isNotA1left(e,r,n)){let e=Jt.WEIGHTS[u][2*t+1];f+=l[t]*e}let w=g+f;if(0!=(1&d)||d>13||d<4)throw new y;let A=(13-d)/2,m=Jt.SYMBOL_WIDEST[A],E=9-m,C=bt.getRSSvalue(a,m,!0),I=bt.getRSSvalue(l,E,!1),_=Jt.EVEN_TOTAL_SUBSET[A],p=Jt.GSUM[A];return new Rt(C*_+I+p,w)}static isNotA1left(t,e,r){return!(0==t.getValue()&&e&&r)}adjustOddEvenCounts(t){let e=et.sum(new Int32Array(this.getOddCounts())),r=et.sum(new Int32Array(this.getEvenCounts())),n=!1,i=!1;e>13?i=!0:e<4&&(n=!0);let o=!1,s=!1;r>13?s=!0:r<4&&(o=!0);let a=e+r-t,l=1==(1&e),c=0==(1&r);if(1==a)if(l){if(c)throw new y;i=!0}else{if(!c)throw new y;s=!0}else if(-1==a)if(l){if(c)throw new y;n=!0}else{if(!c)throw new y;o=!0}else{if(0!=a)throw new y;if(l){if(!c)throw new y;e1)for(let e of this.possibleRightPairs)if(e.getCount()>1&&te.checkChecksum(t,e))return te.constructResult(t,e);throw new y}static addOrTally(t,e){if(null==e)return;let r=!1;for(let n of t)if(n.getValue()===e.getValue()){n.incrementCount(),r=!0;break}r||t.push(e)}reset(){this.possibleLeftPairs.length=0,this.possibleRightPairs.length=0}static constructResult(t,e){let r=4537077*t.getValue()+e.getValue(),n=new String(r).toString(),i=new T;for(let t=13-n.length;t>0;t--)i.append("0");i.append(n);let o=0;for(let t=0;t<13;t++){let e=i.charAt(t).charCodeAt(0)-"0".charCodeAt(0);o+=0==(1&t)?3*e:e}o=10-o%10,10===o&&(o=0),i.append(o.toString());let s=t.getFinderPattern().getResultPoints(),a=e.getFinderPattern().getResultPoints();return new F(i.toString(),null,0,[s[0],s[1],a[0],a[1]],k.RSS_14,(new Date).getTime())}static checkChecksum(t,e){let r=(t.getChecksumPortion()+16*e.getChecksumPortion())%79,n=9*t.getFinderPattern().getValue()+e.getFinderPattern().getValue();return n>72&&n--,n>8&&n--,r===n}decodePair(t,e,r,n){try{let i=this.findFinderPattern(t,e),o=this.parseFoundFinderPattern(t,r,e,i),s=null==n?null:n.get(E.NEED_RESULT_POINT_CALLBACK);if(null!=s){let n=(i[0]+i[1])/2;e&&(n=t.getSize()-1-n),s.foundPossibleResultPoint(new nt(n,r))}let a=this.decodeDataCharacter(t,o,!0),l=this.decodeDataCharacter(t,o,!1);return new $t(1597*a.getValue()+l.getValue(),a.getChecksumPortion()+4*l.getChecksumPortion(),o)}catch(t){return null}}decodeDataCharacter(t,e,r){let n=this.getDataCharacterCounters();for(let t=0;t8&&(r=8);let i=Math.floor(t/2);0==(1&t)?(s[i]=r,l[i]=e-r):(a[i]=r,c[i]=e-r)}this.adjustOddEvenCounts(r,i);let h=0,u=0;for(let t=s.length-1;t>=0;t--)u*=9,u+=s[t],h+=s[t];let d=0,g=0;for(let t=a.length-1;t>=0;t--)d*=9,d+=a[t],g+=a[t];let f=u+3*d;if(r){if(0!=(1&h)||h>12||h<4)throw new y;let t=(12-h)/2,e=te.OUTSIDE_ODD_WIDEST[t],r=9-e,n=bt.getRSSvalue(s,e,!1),i=bt.getRSSvalue(a,r,!0),o=te.OUTSIDE_EVEN_TOTAL_SUBSET[t],l=te.OUTSIDE_GSUM[t];return new Rt(n*o+i+l,f)}{if(0!=(1&g)||g>10||g<4)throw new y;let t=(10-g)/2,e=te.INSIDE_ODD_WIDEST[t],r=9-e,n=bt.getRSSvalue(s,e,!0),i=bt.getRSSvalue(a,r,!1),o=te.INSIDE_ODD_TOTAL_SUBSET[t],l=te.INSIDE_GSUM[t];return new Rt(i*o+n+l,f)}}findFinderPattern(t,e){let r=this.getDecodeFinderCounters();r[0]=0,r[1]=0,r[2]=0,r[3]=0;let n=t.getSize(),i=!1,o=0;for(;o=0&&i!==t.get(o);)o--;o++;const s=n[0]-o,a=this.getDecodeFinderCounters(),l=new Int32Array(a.length);u.arraycopy(a,0,l,1,a.length-1),l[0]=s;const c=this.parseFinderValue(l,te.FINDER_PATTERNS);let h=o,d=n[1];return r&&(h=t.getSize()-1-h,d=t.getSize()-1-d),new Ot(c,[o,n[1]],h,d,e)}adjustOddEvenCounts(t,e){let r=et.sum(new Int32Array(this.getOddCounts())),n=et.sum(new Int32Array(this.getEvenCounts())),i=!1,o=!1,s=!1,a=!1;t?(r>12?o=!0:r<4&&(i=!0),n>12?a=!0:n<4&&(s=!0)):(r>11?o=!0:r<5&&(i=!0),n>10?a=!0:n<4&&(s=!0));let l=r+n-e,c=(1&r)==(t?1:0),h=1==(1&n);if(1===l)if(c){if(h)throw new y;o=!0}else{if(!h)throw new y;a=!0}else if(-1===l)if(c){if(h)throw new y;i=!0}else{if(!h)throw new y;s=!0}else{if(0!==l)throw new y;if(c){if(!h)throw new y;rt.reset()))}}class re{constructor(t,e,r){this.ecCodewords=t,this.ecBlocks=[e],r&&this.ecBlocks.push(r)}getECCodewords(){return this.ecCodewords}getECBlocks(){return this.ecBlocks}}class ne{constructor(t,e){this.count=t,this.dataCodewords=e}getCount(){return this.count}getDataCodewords(){return this.dataCodewords}}class ie{constructor(t,e,r,n,i,o){this.versionNumber=t,this.symbolSizeRows=e,this.symbolSizeColumns=r,this.dataRegionSizeRows=n,this.dataRegionSizeColumns=i,this.ecBlocks=o;let s=0;const a=o.getECCodewords(),l=o.getECBlocks();for(let t of l)s+=t.getCount()*(t.getDataCodewords()+a);this.totalCodewords=s}getVersionNumber(){return this.versionNumber}getSymbolSizeRows(){return this.symbolSizeRows}getSymbolSizeColumns(){return this.symbolSizeColumns}getDataRegionSizeRows(){return this.dataRegionSizeRows}getDataRegionSizeColumns(){return this.dataRegionSizeColumns}getTotalCodewords(){return this.totalCodewords}getECBlocks(){return this.ecBlocks}static getVersionForDimensions(t,e){if(0!=(1&t)||0!=(1&e))throw new C;for(let r of ie.VERSIONS)if(r.symbolSizeRows===t&&r.symbolSizeColumns===e)return r;throw new C}toString(){return""+this.versionNumber}static buildVersions(){return[new ie(1,10,10,8,8,new re(5,new ne(1,3))),new ie(2,12,12,10,10,new re(7,new ne(1,5))),new ie(3,14,14,12,12,new re(10,new ne(1,8))),new ie(4,16,16,14,14,new re(12,new ne(1,12))),new ie(5,18,18,16,16,new re(14,new ne(1,18))),new ie(6,20,20,18,18,new re(18,new ne(1,22))),new ie(7,22,22,20,20,new re(20,new ne(1,30))),new ie(8,24,24,22,22,new re(24,new ne(1,36))),new ie(9,26,26,24,24,new re(28,new ne(1,44))),new ie(10,32,32,14,14,new re(36,new ne(1,62))),new ie(11,36,36,16,16,new re(42,new ne(1,86))),new ie(12,40,40,18,18,new re(48,new ne(1,114))),new ie(13,44,44,20,20,new re(56,new ne(1,144))),new ie(14,48,48,22,22,new re(68,new ne(1,174))),new ie(15,52,52,24,24,new re(42,new ne(2,102))),new ie(16,64,64,14,14,new re(56,new ne(2,140))),new ie(17,72,72,16,16,new re(36,new ne(4,92))),new ie(18,80,80,18,18,new re(48,new ne(4,114))),new ie(19,88,88,20,20,new re(56,new ne(4,144))),new ie(20,96,96,22,22,new re(68,new ne(4,174))),new ie(21,104,104,24,24,new re(56,new ne(6,136))),new ie(22,120,120,18,18,new re(68,new ne(6,175))),new ie(23,132,132,20,20,new re(62,new ne(8,163))),new ie(24,144,144,22,22,new re(62,new ne(8,156),new ne(2,155))),new ie(25,8,18,6,16,new re(7,new ne(1,5))),new ie(26,8,32,6,14,new re(11,new ne(1,10))),new ie(27,12,26,10,24,new re(14,new ne(1,16))),new ie(28,12,36,10,16,new re(18,new ne(1,22))),new ie(29,16,36,14,16,new re(24,new ne(1,32))),new ie(30,16,48,14,22,new re(28,new ne(1,49)))]}}ie.VERSIONS=ie.buildVersions();class oe{constructor(t){const e=t.getHeight();if(e<8||e>144||0!=(1&e))throw new C;this.version=oe.readVersion(t),this.mappingBitMatrix=this.extractDataRegion(t),this.readMappingMatrix=new N(this.mappingBitMatrix.getWidth(),this.mappingBitMatrix.getHeight())}getVersion(){return this.version}static readVersion(t){const e=t.getHeight(),r=t.getWidth();return ie.getVersionForDimensions(e,r)}readCodewords(){const t=new Int8Array(this.version.getTotalCodewords());let e=0,r=4,n=0;const i=this.mappingBitMatrix.getHeight(),o=this.mappingBitMatrix.getWidth();let s=!1,a=!1,l=!1,c=!1;do{if(r!==i||0!==n||s)if(r!==i-2||0!==n||0==(3&o)||a)if(r!==i+4||2!==n||0!=(7&o)||l)if(r!==i-2||0!==n||4!=(7&o)||c){do{r=0&&!this.readMappingMatrix.get(n,r)&&(t[e++]=255&this.readUtah(r,n,i,o)),r-=2,n+=2}while(r>=0&&n=0&&n=0);r+=3,n+=1}else t[e++]=255&this.readCorner4(i,o),r-=2,n+=2,c=!0;else t[e++]=255&this.readCorner3(i,o),r-=2,n+=2,l=!0;else t[e++]=255&this.readCorner2(i,o),r-=2,n+=2,a=!0;else t[e++]=255&this.readCorner1(i,o),r-=2,n+=2,s=!0}while(r7?e-1:e;o[n].codewords[i]=t[h++]}if(h!==t.length)throw new a;return o}getNumDataCodewords(){return this.numDataCodewords}getCodewords(){return this.codewords}}class ae{constructor(t){this.bytes=t,this.byteOffset=0,this.bitOffset=0}getBitOffset(){return this.bitOffset}getByteOffset(){return this.byteOffset}readBits(t){if(t<1||t>32||t>this.available())throw new a(""+t);let e=0,r=this.bitOffset,n=this.byteOffset;const i=this.bytes;if(r>0){const o=8-r,s=t>8-s<>a,t-=s,r+=s,8===r&&(r=0,n++)}if(t>0){for(;t>=8;)e=e<<8|255&i[n],n++,t-=8;if(t>0){const o=8-t,s=255>>o<>o,r+=t}}return this.bitOffset=r,this.byteOffset=n,e}available(){return 8*(this.bytes.length-this.byteOffset)-this.bitOffset}}!function(t){t[t.PAD_ENCODE=0]="PAD_ENCODE",t[t.ASCII_ENCODE=1]="ASCII_ENCODE",t[t.C40_ENCODE=2]="C40_ENCODE",t[t.TEXT_ENCODE=3]="TEXT_ENCODE",t[t.ANSIX12_ENCODE=4]="ANSIX12_ENCODE",t[t.EDIFACT_ENCODE=5]="EDIFACT_ENCODE",t[t.BASE256_ENCODE=6]="BASE256_ENCODE"}(H||(H={}));class le{static decode(t){const e=new ae(t),r=new T,n=new T,i=new Array;let o=H.ASCII_ENCODE;do{if(o===H.ASCII_ENCODE)o=this.decodeAsciiSegment(e,r,n);else{switch(o){case H.C40_ENCODE:this.decodeC40Segment(e,r);break;case H.TEXT_ENCODE:this.decodeTextSegment(e,r);break;case H.ANSIX12_ENCODE:this.decodeAnsiX12Segment(e,r);break;case H.EDIFACT_ENCODE:this.decodeEdifactSegment(e,r);break;case H.BASE256_ENCODE:this.decodeBase256Segment(e,r,i);break;default:throw new C}o=H.ASCII_ENCODE}}while(o!==H.PAD_ENCODE&&e.available()>0);return n.length()>0&&r.append(n.toString()),new W(t,r.toString(),0===i.length?null:i,null)}static decodeAsciiSegment(t,e,r){let n=!1;do{let i=t.readBits(8);if(0===i)throw new C;if(i<=128)return n&&(i+=128),e.append(String.fromCharCode(i-1)),H.ASCII_ENCODE;if(129===i)return H.PAD_ENCODE;if(i<=229){const t=i-130;t<10&&e.append("0"),e.append(""+t)}else switch(i){case 230:return H.C40_ENCODE;case 231:return H.BASE256_ENCODE;case 232:e.append(String.fromCharCode(29));break;case 233:case 234:break;case 235:n=!0;break;case 236:e.append("[)>05"),r.insert(0,"");break;case 237:e.append("[)>06"),r.insert(0,"");break;case 238:return H.ANSIX12_ENCODE;case 239:return H.TEXT_ENCODE;case 240:return H.EDIFACT_ENCODE;case 241:break;default:if(254!==i||0!==t.available())throw new C}}while(t.available()>0);return H.ASCII_ENCODE}static decodeC40Segment(t,e){let r=!1;const n=[];let i=0;do{if(8===t.available())return;const o=t.readBits(8);if(254===o)return;this.parseTwoBytes(o,t.readBits(8),n);for(let t=0;t<3;t++){const o=n[t];switch(i){case 0:if(o<3)i=o+1;else{if(!(o0)}static decodeTextSegment(t,e){let r=!1,n=[],i=0;do{if(8===t.available())return;const o=t.readBits(8);if(254===o)return;this.parseTwoBytes(o,t.readBits(8),n);for(let t=0;t<3;t++){const o=n[t];switch(i){case 0:if(o<3)i=o+1;else{if(!(o0)}static decodeAnsiX12Segment(t,e){const r=[];do{if(8===t.available())return;const n=t.readBits(8);if(254===n)return;this.parseTwoBytes(n,t.readBits(8),r);for(let t=0;t<3;t++){const n=r[t];switch(n){case 0:e.append("\r");break;case 1:e.append("*");break;case 2:e.append(">");break;case 3:e.append(" ");break;default:if(n<14)e.append(String.fromCharCode(n+44));else{if(!(n<40))throw new C;e.append(String.fromCharCode(n+51))}}}}while(t.available()>0)}static parseTwoBytes(t,e,r){let n=(t<<8)+e-1,i=Math.floor(n/1600);r[0]=i,n-=1600*i,i=Math.floor(n/40),r[1]=i,r[2]=n-40*i}static decodeEdifactSegment(t,e){do{if(t.available()<=16)return;for(let r=0;r<4;r++){let r=t.readBits(6);if(31===r){const e=8-t.getBitOffset();return void(8!==e&&t.readBits(e))}0==(32&r)&&(r|=64),e.append(String.fromCharCode(r))}}while(t.available()>0)}static decodeBase256Segment(t,e,r){let n=1+t.getByteOffset();const i=this.unrandomize255State(t.readBits(8),n++);let o;if(o=0===i?t.available()/8|0:i<250?i:250*(i-249)+this.unrandomize255State(t.readBits(8),n++),o<0)throw new C;const s=new Uint8Array(o);for(let e=0;e=0?r:r+256}}le.C40_BASIC_SET_CHARS=["*","*","*"," ","0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"],le.C40_SHIFT2_SET_CHARS=["!",'"',"#","$","%","&","'","(",")","*","+",",","-",".","/",":",";","<","=",">","?","@","[","\\","]","^","_"],le.TEXT_BASIC_SET_CHARS=["*","*","*"," ","0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"],le.TEXT_SHIFT2_SET_CHARS=le.C40_SHIFT2_SET_CHARS,le.TEXT_SHIFT3_SET_CHARS=["`","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","{","|","}","~",String.fromCharCode(127)];class ce{constructor(){this.rsDecoder=new $(K.DATA_MATRIX_FIELD_256)}decode(t){const e=new oe(t),r=e.getVersion(),n=e.readCodewords(),i=se.getDataBlocks(n,r);let o=0;for(let t of i)o+=t.getNumDataCodewords();const s=new Uint8Array(o),a=i.length;for(let t=0;ts&&(c=s,h[0]=e,h[1]=r,h[2]=n,h[3]=i),c>a&&(c=a,h[0]=r,h[1]=n,h[2]=i,h[3]=e),c>l&&(h[0]=n,h[1]=i,h[2]=e,h[3]=r),h}detectSolid2(t){let e=t[0],r=t[1],n=t[2],i=t[3],o=this.transitionsBetween(e,i),s=he.shiftPoint(r,n,4*(o+1)),a=he.shiftPoint(n,r,4*(o+1));return this.transitionsBetween(s,e)this.transitionsBetween(a,h)+this.transitionsBetween(l,h)?c:h:c:this.isValid(h)?h:null}shiftToModuleCenter(t){let e=t[0],r=t[1],n=t[2],i=t[3],o=this.transitionsBetween(e,i)+1,s=this.transitionsBetween(n,i)+1,a=he.shiftPoint(e,r,4*s),l=he.shiftPoint(n,r,4*o);o=this.transitionsBetween(a,i)+1,s=this.transitionsBetween(l,i)+1,1==(1&o)&&(o+=1),1==(1&s)&&(s+=1);let c,h,u=(e.getX()+r.getX()+n.getX()+i.getX())/4,d=(e.getY()+r.getY()+n.getY()+i.getY())/4;return e=he.moveAway(e,u,d),r=he.moveAway(r,u,d),n=he.moveAway(n,u,d),i=he.moveAway(i,u,d),a=he.shiftPoint(e,r,4*s),a=he.shiftPoint(a,i,4*o),c=he.shiftPoint(r,e,4*s),c=he.shiftPoint(c,n,4*o),l=he.shiftPoint(n,i,4*s),l=he.shiftPoint(l,r,4*o),h=he.shiftPoint(i,n,4*s),h=he.shiftPoint(h,e,4*o),[a,c,l,h]}isValid(t){return t.getX()>=0&&t.getX()0&&t.getY()Math.abs(i-r);if(s){let t=r;r=n,n=t,t=i,i=o,o=t}let a=Math.abs(i-r),l=Math.abs(o-n),c=-a/2,h=n0){if(e===o)break;e+=h,c-=a}}return d}}class ue{constructor(){this.decoder=new ce}decode(t,e=null){let r,n;if(null!=e&&e.has(E.PURE_BARCODE)){const e=ue.extractPureBits(t.getBlackMatrix());r=this.decoder.decode(e),n=ue.NO_POINTS}else{const e=new he(t.getBlackMatrix()).detect();r=this.decoder.decode(e.getBits()),n=e.getPoints()}const i=r.getRawBytes(),o=new F(r.getText(),i,8*i.length,n,k.DATA_MATRIX,u.currentTimeMillis()),s=r.getByteSegments();null!=s&&o.putMetadata(X.BYTE_SEGMENTS,s);const a=r.getECLevel();return null!=a&&o.putMetadata(X.ERROR_CORRECTION_LEVEL,a),o}reset(){}static extractPureBits(t){const e=t.getTopLeftOnBit(),r=t.getBottomRightOnBit();if(null==e||null==r)throw new y;const n=this.moduleSize(e,t);let i=e[1];const o=r[1];let s=e[0];const a=(r[0]-s+1)/n,l=(o-i+1)/n;if(a<=0||l<=0)throw new y;const c=n/2;i+=c,s+=c;const h=new N(a,l);for(let e=0;e=de.FOR_BITS.size)throw new a;return de.FOR_BITS.get(t)}}de.FOR_BITS=new Map,de.FOR_VALUE=new Map,de.L=new de(V.L,"L",1),de.M=new de(V.M,"M",0),de.Q=new de(V.Q,"Q",3),de.H=new de(V.H,"H",2);class ge{constructor(t){this.errorCorrectionLevel=de.forBits(t>>3&3),this.dataMask=7&t}static numBitsDiffering(t,e){return w.bitCount(t^e)}static decodeFormatInformation(t,e){const r=ge.doDecodeFormatInformation(t,e);return null!==r?r:ge.doDecodeFormatInformation(t^ge.FORMAT_INFO_MASK_QR,e^ge.FORMAT_INFO_MASK_QR)}static doDecodeFormatInformation(t,e){let r=Number.MAX_SAFE_INTEGER,n=0;for(const i of ge.FORMAT_INFO_DECODE_LOOKUP){const o=i[0];if(o===t||o===e)return new ge(i[1]);let s=ge.numBitsDiffering(t,o);s40)throw new a;return Ae.VERSIONS[t-1]}static decodeVersionInformation(t){let e=Number.MAX_SAFE_INTEGER,r=0;for(let n=0;n6&&(e.setRegion(t-11,0,3,6),e.setRegion(0,t-11,6,3)),e}toString(){return""+this.versionNumber}}Ae.VERSION_DECODE_INFO=Int32Array.from([31892,34236,39577,42195,48118,51042,55367,58893,63784,68472,70749,76311,79154,84390,87683,92361,96236,102084,102881,110507,110734,117786,119615,126325,127568,133589,136944,141498,145311,150283,152622,158308,161089,167017]),Ae.VERSIONS=[new Ae(1,new Int32Array(0),new fe(7,new we(1,19)),new fe(10,new we(1,16)),new fe(13,new we(1,13)),new fe(17,new we(1,9))),new Ae(2,Int32Array.from([6,18]),new fe(10,new we(1,34)),new fe(16,new we(1,28)),new fe(22,new we(1,22)),new fe(28,new we(1,16))),new Ae(3,Int32Array.from([6,22]),new fe(15,new we(1,55)),new fe(26,new we(1,44)),new fe(18,new we(2,17)),new fe(22,new we(2,13))),new Ae(4,Int32Array.from([6,26]),new fe(20,new we(1,80)),new fe(18,new we(2,32)),new fe(26,new we(2,24)),new fe(16,new we(4,9))),new Ae(5,Int32Array.from([6,30]),new fe(26,new we(1,108)),new fe(24,new we(2,43)),new fe(18,new we(2,15),new we(2,16)),new fe(22,new we(2,11),new we(2,12))),new Ae(6,Int32Array.from([6,34]),new fe(18,new we(2,68)),new fe(16,new we(4,27)),new fe(24,new we(4,19)),new fe(28,new we(4,15))),new Ae(7,Int32Array.from([6,22,38]),new fe(20,new we(2,78)),new fe(18,new we(4,31)),new fe(18,new we(2,14),new we(4,15)),new fe(26,new we(4,13),new we(1,14))),new Ae(8,Int32Array.from([6,24,42]),new fe(24,new we(2,97)),new fe(22,new we(2,38),new we(2,39)),new fe(22,new we(4,18),new we(2,19)),new fe(26,new we(4,14),new we(2,15))),new Ae(9,Int32Array.from([6,26,46]),new fe(30,new we(2,116)),new fe(22,new we(3,36),new we(2,37)),new fe(20,new we(4,16),new we(4,17)),new fe(24,new we(4,12),new we(4,13))),new Ae(10,Int32Array.from([6,28,50]),new fe(18,new we(2,68),new we(2,69)),new fe(26,new we(4,43),new we(1,44)),new fe(24,new we(6,19),new we(2,20)),new fe(28,new we(6,15),new we(2,16))),new Ae(11,Int32Array.from([6,30,54]),new fe(20,new we(4,81)),new fe(30,new we(1,50),new we(4,51)),new fe(28,new we(4,22),new we(4,23)),new fe(24,new we(3,12),new we(8,13))),new Ae(12,Int32Array.from([6,32,58]),new fe(24,new we(2,92),new we(2,93)),new fe(22,new we(6,36),new we(2,37)),new fe(26,new we(4,20),new we(6,21)),new fe(28,new we(7,14),new we(4,15))),new Ae(13,Int32Array.from([6,34,62]),new fe(26,new we(4,107)),new fe(22,new we(8,37),new we(1,38)),new fe(24,new we(8,20),new we(4,21)),new fe(22,new we(12,11),new we(4,12))),new Ae(14,Int32Array.from([6,26,46,66]),new fe(30,new we(3,115),new we(1,116)),new fe(24,new we(4,40),new we(5,41)),new fe(20,new we(11,16),new we(5,17)),new fe(24,new we(11,12),new we(5,13))),new Ae(15,Int32Array.from([6,26,48,70]),new fe(22,new we(5,87),new we(1,88)),new fe(24,new we(5,41),new we(5,42)),new fe(30,new we(5,24),new we(7,25)),new fe(24,new we(11,12),new we(7,13))),new Ae(16,Int32Array.from([6,26,50,74]),new fe(24,new we(5,98),new we(1,99)),new fe(28,new we(7,45),new we(3,46)),new fe(24,new we(15,19),new we(2,20)),new fe(30,new we(3,15),new we(13,16))),new Ae(17,Int32Array.from([6,30,54,78]),new fe(28,new we(1,107),new we(5,108)),new fe(28,new we(10,46),new we(1,47)),new fe(28,new we(1,22),new we(15,23)),new fe(28,new we(2,14),new we(17,15))),new Ae(18,Int32Array.from([6,30,56,82]),new fe(30,new we(5,120),new we(1,121)),new fe(26,new we(9,43),new we(4,44)),new fe(28,new we(17,22),new we(1,23)),new fe(28,new we(2,14),new we(19,15))),new Ae(19,Int32Array.from([6,30,58,86]),new fe(28,new we(3,113),new we(4,114)),new fe(26,new we(3,44),new we(11,45)),new fe(26,new we(17,21),new we(4,22)),new fe(26,new we(9,13),new we(16,14))),new Ae(20,Int32Array.from([6,34,62,90]),new fe(28,new we(3,107),new we(5,108)),new fe(26,new we(3,41),new we(13,42)),new fe(30,new we(15,24),new we(5,25)),new fe(28,new we(15,15),new we(10,16))),new Ae(21,Int32Array.from([6,28,50,72,94]),new fe(28,new we(4,116),new we(4,117)),new fe(26,new we(17,42)),new fe(28,new we(17,22),new we(6,23)),new fe(30,new we(19,16),new we(6,17))),new Ae(22,Int32Array.from([6,26,50,74,98]),new fe(28,new we(2,111),new we(7,112)),new fe(28,new we(17,46)),new fe(30,new we(7,24),new we(16,25)),new fe(24,new we(34,13))),new Ae(23,Int32Array.from([6,30,54,78,102]),new fe(30,new we(4,121),new we(5,122)),new fe(28,new we(4,47),new we(14,48)),new fe(30,new we(11,24),new we(14,25)),new fe(30,new we(16,15),new we(14,16))),new Ae(24,Int32Array.from([6,28,54,80,106]),new fe(30,new we(6,117),new we(4,118)),new fe(28,new we(6,45),new we(14,46)),new fe(30,new we(11,24),new we(16,25)),new fe(30,new we(30,16),new we(2,17))),new Ae(25,Int32Array.from([6,32,58,84,110]),new fe(26,new we(8,106),new we(4,107)),new fe(28,new we(8,47),new we(13,48)),new fe(30,new we(7,24),new we(22,25)),new fe(30,new we(22,15),new we(13,16))),new Ae(26,Int32Array.from([6,30,58,86,114]),new fe(28,new we(10,114),new we(2,115)),new fe(28,new we(19,46),new we(4,47)),new fe(28,new we(28,22),new we(6,23)),new fe(30,new we(33,16),new we(4,17))),new Ae(27,Int32Array.from([6,34,62,90,118]),new fe(30,new we(8,122),new we(4,123)),new fe(28,new we(22,45),new we(3,46)),new fe(30,new we(8,23),new we(26,24)),new fe(30,new we(12,15),new we(28,16))),new Ae(28,Int32Array.from([6,26,50,74,98,122]),new fe(30,new we(3,117),new we(10,118)),new fe(28,new we(3,45),new we(23,46)),new fe(30,new we(4,24),new we(31,25)),new fe(30,new we(11,15),new we(31,16))),new Ae(29,Int32Array.from([6,30,54,78,102,126]),new fe(30,new we(7,116),new we(7,117)),new fe(28,new we(21,45),new we(7,46)),new fe(30,new we(1,23),new we(37,24)),new fe(30,new we(19,15),new we(26,16))),new Ae(30,Int32Array.from([6,26,52,78,104,130]),new fe(30,new we(5,115),new we(10,116)),new fe(28,new we(19,47),new we(10,48)),new fe(30,new we(15,24),new we(25,25)),new fe(30,new we(23,15),new we(25,16))),new Ae(31,Int32Array.from([6,30,56,82,108,134]),new fe(30,new we(13,115),new we(3,116)),new fe(28,new we(2,46),new we(29,47)),new fe(30,new we(42,24),new we(1,25)),new fe(30,new we(23,15),new we(28,16))),new Ae(32,Int32Array.from([6,34,60,86,112,138]),new fe(30,new we(17,115)),new fe(28,new we(10,46),new we(23,47)),new fe(30,new we(10,24),new we(35,25)),new fe(30,new we(19,15),new we(35,16))),new Ae(33,Int32Array.from([6,30,58,86,114,142]),new fe(30,new we(17,115),new we(1,116)),new fe(28,new we(14,46),new we(21,47)),new fe(30,new we(29,24),new we(19,25)),new fe(30,new we(11,15),new we(46,16))),new Ae(34,Int32Array.from([6,34,62,90,118,146]),new fe(30,new we(13,115),new we(6,116)),new fe(28,new we(14,46),new we(23,47)),new fe(30,new we(44,24),new we(7,25)),new fe(30,new we(59,16),new we(1,17))),new Ae(35,Int32Array.from([6,30,54,78,102,126,150]),new fe(30,new we(12,121),new we(7,122)),new fe(28,new we(12,47),new we(26,48)),new fe(30,new we(39,24),new we(14,25)),new fe(30,new we(22,15),new we(41,16))),new Ae(36,Int32Array.from([6,24,50,76,102,128,154]),new fe(30,new we(6,121),new we(14,122)),new fe(28,new we(6,47),new we(34,48)),new fe(30,new we(46,24),new we(10,25)),new fe(30,new we(2,15),new we(64,16))),new Ae(37,Int32Array.from([6,28,54,80,106,132,158]),new fe(30,new we(17,122),new we(4,123)),new fe(28,new we(29,46),new we(14,47)),new fe(30,new we(49,24),new we(10,25)),new fe(30,new we(24,15),new we(46,16))),new Ae(38,Int32Array.from([6,32,58,84,110,136,162]),new fe(30,new we(4,122),new we(18,123)),new fe(28,new we(13,46),new we(32,47)),new fe(30,new we(48,24),new we(14,25)),new fe(30,new we(42,15),new we(32,16))),new Ae(39,Int32Array.from([6,26,54,82,110,138,166]),new fe(30,new we(20,117),new we(4,118)),new fe(28,new we(40,47),new we(7,48)),new fe(30,new we(43,24),new we(22,25)),new fe(30,new we(10,15),new we(67,16))),new Ae(40,Int32Array.from([6,30,58,86,114,142,170]),new fe(30,new we(19,118),new we(6,119)),new fe(28,new we(18,47),new we(31,48)),new fe(30,new we(34,24),new we(34,25)),new fe(30,new we(20,15),new we(61,16)))],function(t){t[t.DATA_MASK_000=0]="DATA_MASK_000",t[t.DATA_MASK_001=1]="DATA_MASK_001",t[t.DATA_MASK_010=2]="DATA_MASK_010",t[t.DATA_MASK_011=3]="DATA_MASK_011",t[t.DATA_MASK_100=4]="DATA_MASK_100",t[t.DATA_MASK_101=5]="DATA_MASK_101",t[t.DATA_MASK_110=6]="DATA_MASK_110",t[t.DATA_MASK_111=7]="DATA_MASK_111"}(z||(z={}));class me{constructor(t,e){this.value=t,this.isMasked=e}unmaskBitMatrix(t,e){for(let r=0;r0==(t+e&1)))],[z.DATA_MASK_001,new me(z.DATA_MASK_001,((t,e)=>0==(1&t)))],[z.DATA_MASK_010,new me(z.DATA_MASK_010,((t,e)=>e%3==0))],[z.DATA_MASK_011,new me(z.DATA_MASK_011,((t,e)=>(t+e)%3==0))],[z.DATA_MASK_100,new me(z.DATA_MASK_100,((t,e)=>0==(Math.floor(t/2)+Math.floor(e/3)&1)))],[z.DATA_MASK_101,new me(z.DATA_MASK_101,((t,e)=>t*e%6==0))],[z.DATA_MASK_110,new me(z.DATA_MASK_110,((t,e)=>t*e%6<3))],[z.DATA_MASK_111,new me(z.DATA_MASK_111,((t,e)=>0==(t+e+t*e%3&1)))]]);class Ee{constructor(t){const e=t.getHeight();if(e<21||1!=(3&e))throw new C;this.bitMatrix=t}readFormatInformation(){if(null!==this.parsedFormatInfo&&void 0!==this.parsedFormatInfo)return this.parsedFormatInfo;let t=0;for(let e=0;e<6;e++)t=this.copyBit(e,8,t);t=this.copyBit(7,8,t),t=this.copyBit(8,8,t),t=this.copyBit(8,7,t);for(let e=5;e>=0;e--)t=this.copyBit(8,e,t);const e=this.bitMatrix.getHeight();let r=0;const n=e-7;for(let t=e-1;t>=n;t--)r=this.copyBit(8,t,r);for(let t=e-8;t=0;e--)for(let i=t-9;i>=n;i--)r=this.copyBit(i,e,r);let i=Ae.decodeVersionInformation(r);if(null!==i&&i.getDimensionForVersion()===t)return this.parsedVersion=i,i;r=0;for(let e=5;e>=0;e--)for(let i=t-9;i>=n;i--)r=this.copyBit(e,i,r);if(i=Ae.decodeVersionInformation(r),null!==i&&i.getDimensionForVersion()===t)return this.parsedVersion=i,i;throw new C}copyBit(t,e,r){return(this.isMirror?this.bitMatrix.get(e,t):this.bitMatrix.get(t,e))?r<<1|1:r<<1}readCodewords(){const t=this.readFormatInformation(),e=this.readVersion(),r=me.values.get(t.getDataMask()),n=this.bitMatrix.getHeight();r.unmaskBitMatrix(this.bitMatrix,n);const i=e.buildFunctionPattern();let o=!0;const s=new Uint8Array(e.getTotalCodewords());let a=0,l=0,c=0;for(let t=n-1;t>0;t-=2){6===t&&t--;for(let e=0;e=0&&s[h].codewords.length!==c;)h--;h++;const u=c-n.getECCodewordsPerBlock();let d=0;for(let e=0;et.available())throw new C;const n=new Uint8Array(2*r);let i=0;for(;r>0;){const e=t.readBits(13);let o=e/96<<8&4294967295|e%96;o+=o<959?41377:42657,n[i]=o>>8&255,n[i+1]=255&o,i+=2,r--}try{e.append(p.decode(n,S.GB2312))}catch(t){throw new C(t)}}static decodeKanjiSegment(t,e,r){if(13*r>t.available())throw new C;const n=new Uint8Array(2*r);let i=0;for(;r>0;){const e=t.readBits(13);let o=e/192<<8&4294967295|e%192;o+=o<7936?33088:49472,n[i]=o>>8,n[i+1]=o,i+=2,r--}try{e.append(p.decode(n,S.SHIFT_JIS))}catch(t){throw new C(t)}}static decodeByteSegment(t,e,r,n,i,o){if(8*r>t.available())throw new C;const s=new Uint8Array(r);for(let e=0;e=_e.ALPHANUMERIC_CHARS.length)throw new C;return _e.ALPHANUMERIC_CHARS[t]}static decodeAlphanumericSegment(t,e,r,n){const i=e.length();for(;r>1;){if(t.available()<11)throw new C;const n=t.readBits(11);e.append(_e.toAlphaNumericChar(Math.floor(n/45))),e.append(_e.toAlphaNumericChar(n%45)),r-=2}if(1===r){if(t.available()<6)throw new C;e.append(_e.toAlphaNumericChar(t.readBits(6)))}if(n)for(let t=i;t=3;){if(t.available()<10)throw new C;const n=t.readBits(10);if(n>=1e3)throw new C;e.append(_e.toAlphaNumericChar(Math.floor(n/100))),e.append(_e.toAlphaNumericChar(Math.floor(n/10)%10)),e.append(_e.toAlphaNumericChar(n%10)),r-=3}if(2===r){if(t.available()<7)throw new C;const r=t.readBits(7);if(r>=100)throw new C;e.append(_e.toAlphaNumericChar(Math.floor(r/10))),e.append(_e.toAlphaNumericChar(r%10))}else if(1===r){if(t.available()<4)throw new C;const r=t.readBits(4);if(r>=10)throw new C;e.append(_e.toAlphaNumericChar(r))}}static parseECIValue(t){const e=t.readBits(8);if(0==(128&e))return 127&e;if(128==(192&e))return(63&e)<<8&4294967295|t.readBits(8);if(192==(224&e))return(31&e)<<16&4294967295|t.readBits(16);throw new C}}_e.ALPHANUMERIC_CHARS="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:",_e.GB2312_SUBSET=1;class pe{constructor(t){this.mirrored=t}isMirrored(){return this.mirrored}applyMirroredCorrection(t){if(!this.mirrored||null===t||t.length<3)return;const e=t[0];t[0]=t[2],t[2]=e}}class Se{constructor(){this.rsDecoder=new $(K.QR_CODE_FIELD_256)}decodeBooleanArray(t,e){return this.decodeBitMatrix(N.parseFromBooleanArray(t),e)}decodeBitMatrix(t,e){const r=new Ee(t);let n=null;try{return this.decodeBitMatrixParser(r,e)}catch(t){n=t}try{r.remask(),r.setMirror(!0),r.readVersion(),r.readFormatInformation(),r.mirror();const t=this.decodeBitMatrixParser(r,e);return t.setOther(new pe(!0)),t}catch(t){if(null!==n)throw n;throw t}}decodeBitMatrixParser(t,e){const r=t.readVersion(),n=t.readFormatInformation().getErrorCorrectionLevel(),i=t.readCodewords(),o=Ce.getDataBlocks(i,r,n);let s=0;for(const t of o)s+=t.getNumDataCodewords();const a=new Uint8Array(s);let l=0;for(const t of o){const e=t.getCodewords(),r=t.getNumDataCodewords();this.correctErrors(e,r);for(let t=0;t=r)return!1;return!0}crossCheckVertical(t,e,r,n){const i=this.image,o=i.getHeight(),s=this.crossCheckStateCount;s[0]=0,s[1]=0,s[2]=0;let a=t;for(;a>=0&&i.get(e,a)&&s[1]<=r;)s[1]++,a--;if(a<0||s[1]>r)return NaN;for(;a>=0&&!i.get(e,a)&&s[0]<=r;)s[0]++,a--;if(s[0]>r)return NaN;for(a=t+1;ar)return NaN;for(;ar)return NaN;const l=s[0]+s[1]+s[2];return 5*Math.abs(l-n)>=2*n?NaN:this.foundPatternCross(s)?Ne.centerFromEnd(s,a):NaN}handlePossibleCenter(t,e,r){const n=t[0]+t[1]+t[2],i=Ne.centerFromEnd(t,r),o=this.crossCheckVertical(e,i,2*t[1],n);if(!isNaN(o)){const e=(t[0]+t[1]+t[2])/3;for(const t of this.possibleCenters)if(t.aboutEquals(e,o,i))return t.combineEstimate(o,i,e);const r=new Te(i,o,e);this.possibleCenters.push(r),null!==this.resultPointCallback&&void 0!==this.resultPointCallback&&this.resultPointCallback.foundPossibleResultPoint(r)}return null}}class ye extends nt{constructor(t,e,r,n){super(t,e),this.estimatedModuleSize=r,this.count=n,void 0===n&&(this.count=1)}getEstimatedModuleSize(){return this.estimatedModuleSize}getCount(){return this.count}aboutEquals(t,e,r){if(Math.abs(e-this.getY())<=t&&Math.abs(r-this.getX())<=t){const e=Math.abs(t-this.estimatedModuleSize);return e<=1||e<=this.estimatedModuleSize}return!1}combineEstimate(t,e,r){const n=this.count+1,i=(this.count*this.getX()+e)/n,o=(this.count*this.getY()+t)/n,s=(this.count*this.estimatedModuleSize+r)/n;return new ye(i,o,s,n)}}class Me{constructor(t){this.bottomLeft=t[0],this.topLeft=t[1],this.topRight=t[2]}getBottomLeft(){return this.bottomLeft}getTopLeft(){return this.topLeft}getTopRight(){return this.topRight}}class De{constructor(t,e){this.image=t,this.resultPointCallback=e,this.possibleCenters=[],this.crossCheckStateCount=new Int32Array(5),this.resultPointCallback=e}getImage(){return this.image}getPossibleCenters(){return this.possibleCenters}find(t){const e=null!=t&&void 0!==t.get(E.TRY_HARDER),r=null!=t&&void 0!==t.get(E.PURE_BARCODE),n=this.image,i=n.getHeight(),o=n.getWidth();let s=Math.floor(3*i/(4*De.MAX_MODULES));(sl[2]&&(t+=e-l[2]-s,i=o-1)}e=0,l[0]=0,l[1]=0,l[2]=0,l[3]=0,l[4]=0}else l[0]=l[2],l[1]=l[3],l[2]=l[4],l[3]=1,l[4]=0,e=3;else l[++e]++;else l[e]++;De.foundPatternCross(l)&&!0===this.handlePossibleCenter(l,t,o,r)&&(s=l[0],this.hasSkipped&&(a=this.haveMultiplyConfirmedCenters()))}const c=this.selectBestPatterns();return nt.orderBestPatterns(c),new Me(c)}static centerFromEnd(t,e){return e-t[4]-t[3]-t[2]/2}static foundPatternCross(t){let e=0;for(let r=0;r<5;r++){const n=t[r];if(0===n)return!1;e+=n}if(e<7)return!1;const r=e/7,n=r/2;return Math.abs(r-t[0])=o&&e>=o&&s.get(e-o,t-o);)i[2]++,o++;if(t=o&&e>=o&&!s.get(e-o,t-o)&&i[1]<=r;)i[1]++,o++;if(tr)return!1;for(;t>=o&&e>=o&&s.get(e-o,t-o)&&i[0]<=r;)i[0]++,o++;if(i[0]>r)return!1;const a=s.getHeight(),l=s.getWidth();for(o=1;t+o=a||e+o>=l)return!1;for(;t+o=a||e+o>=l||i[3]>=r)return!1;for(;t+o=r)return!1;const c=i[0]+i[1]+i[2]+i[3]+i[4];return Math.abs(c-n)<2*n&&De.foundPatternCross(i)}crossCheckVertical(t,e,r,n){const i=this.image,o=i.getHeight(),s=this.getCrossCheckStateCount();let a=t;for(;a>=0&&i.get(e,a);)s[2]++,a--;if(a<0)return NaN;for(;a>=0&&!i.get(e,a)&&s[1]<=r;)s[1]++,a--;if(a<0||s[1]>r)return NaN;for(;a>=0&&i.get(e,a)&&s[0]<=r;)s[0]++,a--;if(s[0]>r)return NaN;for(a=t+1;a=r)return NaN;for(;a=r)return NaN;const l=s[0]+s[1]+s[2]+s[3]+s[4];return 5*Math.abs(l-n)>=2*n?NaN:De.foundPatternCross(s)?De.centerFromEnd(s,a):NaN}crossCheckHorizontal(t,e,r,n){const i=this.image,o=i.getWidth(),s=this.getCrossCheckStateCount();let a=t;for(;a>=0&&i.get(a,e);)s[2]++,a--;if(a<0)return NaN;for(;a>=0&&!i.get(a,e)&&s[1]<=r;)s[1]++,a--;if(a<0||s[1]>r)return NaN;for(;a>=0&&i.get(a,e)&&s[0]<=r;)s[0]++,a--;if(s[0]>r)return NaN;for(a=t+1;a=r)return NaN;for(;a=r)return NaN;const l=s[0]+s[1]+s[2]+s[3]+s[4];return 5*Math.abs(l-n)>=n?NaN:De.foundPatternCross(s)?De.centerFromEnd(s,a):NaN}handlePossibleCenter(t,e,r,n){const i=t[0]+t[1]+t[2]+t[3]+t[4];let o=De.centerFromEnd(t,r),s=this.crossCheckVertical(e,Math.floor(o),t[2],i);if(!isNaN(s)&&(o=this.crossCheckHorizontal(Math.floor(o),Math.floor(s),t[2],i),!isNaN(o)&&(!n||this.crossCheckDiagonal(Math.floor(s),Math.floor(o),t[2],i)))){const t=i/7;let e=!1;const r=this.possibleCenters;for(let n=0,i=r.length;n=De.CENTER_QUORUM){if(null!=t)return this.hasSkipped=!0,Math.floor((Math.abs(t.getX()-e.getX())-Math.abs(t.getY()-e.getY()))/2);t=e}return 0}haveMultiplyConfirmedCenters(){let t=0,e=0;const r=this.possibleCenters.length;for(const r of this.possibleCenters)r.getCount()>=De.CENTER_QUORUM&&(t++,e+=r.getEstimatedModuleSize());if(t<3)return!1;const n=e/r;let i=0;for(const t of this.possibleCenters)i+=Math.abs(t.getEstimatedModuleSize()-n);return i<=.05*e}selectBestPatterns(){const t=this.possibleCenters.length;if(t<3)throw new y;const e=this.possibleCenters;let r;if(t>3){let n=0,i=0;for(const t of this.possibleCenters){const e=t.getEstimatedModuleSize();n+=e,i+=e*e}r=n/t;let o=Math.sqrt(i/t-r*r);e.sort(((t,e)=>{const n=Math.abs(e.getEstimatedModuleSize()-r),i=Math.abs(t.getEstimatedModuleSize()-r);return ni?1:0}));const s=Math.max(.2*r,o);for(let t=0;t3;t++){const n=e[t];Math.abs(n.getEstimatedModuleSize()-r)>s&&(e.splice(t,1),t--)}}if(e.length>3){let t=0;for(const r of e)t+=r.getEstimatedModuleSize();r=t/e.length,e.sort(((t,e)=>{if(e.getCount()===t.getCount()){const n=Math.abs(e.getEstimatedModuleSize()-r),i=Math.abs(t.getEstimatedModuleSize()-r);return ni?-1:0}return e.getCount()-t.getCount()})),e.splice(3)}return[e[0],e[1],e[2]]}}De.CENTER_QUORUM=2,De.MIN_SKIP=3,De.MAX_MODULES=57;class Re{constructor(t){this.image=t}getImage(){return this.image}getResultPointCallback(){return this.resultPointCallback}detect(t){this.resultPointCallback=null==t?null:t.get(E.NEED_RESULT_POINT_CALLBACK);const e=new De(this.image,this.resultPointCallback).find(t);return this.processFinderPatternInfo(e)}processFinderPatternInfo(t){const e=t.getTopLeft(),r=t.getTopRight(),n=t.getBottomLeft(),i=this.calculateModuleSize(e,r,n);if(i<1)throw new y("No pattern found in proccess finder.");const o=Re.computeDimension(e,r,n,i),s=Ae.getProvisionalVersionForDimension(o),a=s.getDimensionForVersion()-7;let l=null;if(s.getAlignmentPatternCenters().length>0){const t=r.getX()-e.getX()+n.getX(),o=r.getY()-e.getY()+n.getY(),s=1-3/a,c=Math.floor(e.getX()+s*(t-e.getX())),h=Math.floor(e.getY()+s*(o-e.getY()));for(let t=4;t<=16;t<<=1)try{l=this.findAlignmentInRegion(i,c,h,t);break}catch(t){if(!(t instanceof y))throw t}}const c=Re.createTransform(e,r,n,l,o),h=Re.sampleGrid(this.image,c,o);let u;return u=null===l?[n,e,r]:[n,e,r,l],new it(h,u)}static createTransform(t,e,r,n,i){const o=i-3.5;let s,a,l,c;return null!==n?(s=n.getX(),a=n.getY(),l=o-3,c=l):(s=e.getX()-t.getX()+r.getX(),a=e.getY()-t.getY()+r.getY(),l=o,c=o),lt.quadrilateralToQuadrilateral(3.5,3.5,o,3.5,l,c,3.5,o,t.getX(),t.getY(),e.getX(),e.getY(),s,a,r.getX(),r.getY())}static sampleGrid(t,e,r){return ht.getInstance().sampleGridWithTransform(t,r,r,e)}static computeDimension(t,e,r,n){const i=et.round(nt.distance(t,e)/n),o=et.round(nt.distance(t,r)/n);let s=Math.floor((i+o)/2)+7;switch(3&s){case 0:s++;break;case 2:s--;break;case 3:throw new y("Dimensions could be not found.")}return s}calculateModuleSize(t,e,r){return(this.calculateModuleSizeOneWay(t,e)+this.calculateModuleSizeOneWay(t,r))/2}calculateModuleSizeOneWay(t,e){const r=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(t.getX()),Math.floor(t.getY()),Math.floor(e.getX()),Math.floor(e.getY())),n=this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(e.getX()),Math.floor(e.getY()),Math.floor(t.getX()),Math.floor(t.getY()));return isNaN(r)?n/7:isNaN(n)?r/7:(r+n)/14}sizeOfBlackWhiteBlackRunBothWays(t,e,r,n){let i=this.sizeOfBlackWhiteBlackRun(t,e,r,n),o=1,s=t-(r-t);s<0?(o=t/(t-s),s=0):s>=this.image.getWidth()&&(o=(this.image.getWidth()-1-t)/(s-t),s=this.image.getWidth()-1);let a=Math.floor(e-(n-e)*o);return o=1,a<0?(o=e/(e-a),a=0):a>=this.image.getHeight()&&(o=(this.image.getHeight()-1-e)/(a-e),a=this.image.getHeight()-1),s=Math.floor(t+(s-t)*o),i+=this.sizeOfBlackWhiteBlackRun(t,e,s,a),i-1}sizeOfBlackWhiteBlackRun(t,e,r,n){const i=Math.abs(n-e)>Math.abs(r-t);if(i){let i=t;t=e,e=i,i=r,r=n,n=i}const o=Math.abs(r-t),s=Math.abs(n-e);let a=-o/2;const l=t0){if(d===n)break;d+=c,a-=o}}return 2===h?et.distance(r+l,n,t,e):NaN}findAlignmentInRegion(t,e,r,n){const i=Math.floor(n*t),o=Math.max(0,e-i),s=Math.min(this.image.getWidth()-1,e+i);if(s-o<3*t)throw new y("Alignment top exceeds estimated module size.");const a=Math.max(0,r-i),l=Math.min(this.image.getHeight()-1,r+i);if(l-a<3*t)throw new y("Alignment bottom exceeds estimated module size.");return new Ne(this.image,o,a,s-o,l-a,t,this.resultPointCallback).find()}}class Oe{constructor(){this.decoder=new Se}getDecoder(){return this.decoder}decode(t,e){let r,n;if(null!=e&&void 0!==e.get(E.PURE_BARCODE)){const i=Oe.extractPureBits(t.getBlackMatrix());r=this.decoder.decodeBitMatrix(i,e),n=Oe.NO_POINTS}else{const i=new Re(t.getBlackMatrix()).detect(e);r=this.decoder.decodeBitMatrix(i.getBits(),e),n=i.getPoints()}r.getOther()instanceof pe&&r.getOther().applyMirroredCorrection(n);const i=new F(r.getText(),r.getRawBytes(),void 0,n,k.QR_CODE,void 0),o=r.getByteSegments();null!==o&&i.putMetadata(X.BYTE_SEGMENTS,o);const s=r.getECLevel();return null!==s&&i.putMetadata(X.ERROR_CORRECTION_LEVEL,s),r.hasStructuredAppend()&&(i.putMetadata(X.STRUCTURED_APPEND_SEQUENCE,r.getStructuredAppendSequenceNumber()),i.putMetadata(X.STRUCTURED_APPEND_PARITY,r.getStructuredAppendParity())),i}reset(){}static extractPureBits(t){const e=t.getTopLeftOnBit(),r=t.getBottomRightOnBit();if(null===e||null===r)throw new y;const n=this.moduleSize(e,t);let i=e[1],o=r[1],s=e[0],a=r[0];if(s>=a||i>=o)throw new y;if(o-i!=a-s&&(a=s+(o-i),a>=t.getWidth()))throw new y;const l=Math.round((a-s+1)/n),c=Math.round((o-i+1)/n);if(l<=0||c<=0)throw new y;if(c!==l)throw new y;const h=Math.floor(n/2);i+=h,s+=h;const u=s+Math.floor((l-1)*n)-a;if(u>0){if(u>h)throw new y;s-=u}const d=i+Math.floor((c-1)*n)-o;if(d>0){if(d>h)throw new y;i-=d}const g=new N(l,c);for(let e=0;e0;){const s=Be.findGuardPattern(t,i,--n,r,!1,o,l);if(null==s){n++;break}e=s}s[0]=new nt(e[0],n),s[1]=new nt(e[1],n),a=!0;break}}let c=n+1;if(a){let n=0,i=Int32Array.from([Math.trunc(s[0].getX()),Math.trunc(s[1].getX())]);for(;cBe.SKIPPED_ROW_COUNT_MAX)break;n++}}c-=n+1,s[2]=new nt(i[0],c),s[3]=new nt(i[1],c)}return c-n0&&l++o?n-o:o-n;if(l>r)return 1/0;a+=l}return a/i}}Be.INDEXES_START_PATTERN=Int32Array.from([0,4,1,5]),Be.INDEXES_STOP_PATTERN=Int32Array.from([6,2,7,3]),Be.MAX_AVG_VARIANCE=.42,Be.MAX_INDIVIDUAL_VARIANCE=.8,Be.START_PATTERN=Int32Array.from([8,1,1,1,1,1,1,3]),Be.STOP_PATTERN=Int32Array.from([7,1,1,3,1,1,1,2,1]),Be.MAX_PIXEL_DRIFT=3,Be.MAX_PATTERN_DRIFT=5,Be.SKIPPED_ROW_COUNT_MAX=25,Be.ROW_STEP=5,Be.BARCODE_MIN_HEIGHT=10;class Pe{constructor(t,e){if(0===e.length)throw new a;this.field=t;let r=e.length;if(r>1&&0===e[0]){let t=1;for(;tr.length){let t=e;e=r,r=t}let n=new Int32Array(r.length),i=r.length-e.length;u.arraycopy(r,0,n,0,i);for(let t=i;t=0;e--){let r=this.getCoefficient(e);0!==r&&(r<0?(t.append(" - "),r=-r):t.length()>0&&t.append(" + "),0!==e&&1===r||t.append(r),0!==e&&(1===e?t.append("x"):(t.append("x^"),t.append(e))))}return t.toString()}}class ve extends class{add(t,e){return(t+e)%this.modulus}subtract(t,e){return(this.modulus+t-e)%this.modulus}exp(t){return this.expTable[t]}log(t){if(0===t)throw new a;return this.logTable[t]}inverse(t){if(0===t)throw new Q;return this.expTable[this.modulus-this.logTable[t]-1]}multiply(t,e){return 0===t||0===e?0:this.expTable[(this.logTable[t]+this.logTable[e])%(this.modulus-1)]}getSize(){return this.modulus}equals(t){return t===this}}{constructor(t,e){super(),this.modulus=t,this.expTable=new Int32Array(t),this.logTable=new Int32Array(t);let r=1;for(let n=0;n0;t--){let r=n.evaluateAt(this.field.exp(t));i[e-t]=r,0!==r&&(o=!0)}if(!o)return 0;let s=this.field.getOne();if(null!=r)for(const e of r){let r=this.field.exp(t.length-1-e),n=new Pe(this.field,new Int32Array([this.field.subtract(0,r),1]));s=s.multiply(n)}let a=new Pe(this.field,i),l=this.runEuclideanAlgorithm(this.field.buildMonomial(e,1),a,e),h=l[0],u=l[1],d=this.findErrorLocations(h),g=this.findErrorMagnitudes(u,h,d);for(let e=0;e=Math.round(r/2);){let t=n,e=o;if(n=i,o=s,n.isZero())throw c.getChecksumInstance();i=t;let r=this.field.getZero(),a=n.getCoefficient(n.getDegree()),l=this.field.inverse(a);for(;i.getDegree()>=n.getDegree()&&!i.isZero();){let t=i.getDegree()-n.getDegree(),e=this.field.multiply(i.getCoefficient(i.getDegree()),l);r=r.add(this.field.buildMonomial(t,e)),i=i.subtract(n.multiplyByMonomial(t,e))}s=r.multiply(o).subtract(e).negative()}let a=s.getCoefficient(0);if(0===a)throw c.getChecksumInstance();let l=this.field.inverse(a);return[s.multiply(l),i.multiply(l)]}findErrorLocations(t){let e=t.getDegree(),r=new Int32Array(e),n=0;for(let i=1;i0){let e=r?this.topLeft:this.topRight,i=Math.trunc(e.getY()-t);i<0&&(i=0);let s=new nt(e.getX(),i);r?n=s:o=s}if(e>0){let t=r?this.bottomLeft:this.bottomRight,n=Math.trunc(t.getY()+e);n>=this.image.getHeight()&&(n=this.image.getHeight()-1);let o=new nt(t.getX(),n);r?i=o:s=o}return new xe(this.image,n,i,o,s)}getMinX(){return this.minX}getMaxX(){return this.maxX}getMinY(){return this.minY}getMaxY(){return this.maxY}getTopLeft(){return this.topLeft}getTopRight(){return this.topRight}getBottomLeft(){return this.bottomLeft}getBottomRight(){return this.bottomRight}}class ke{constructor(t,e,r,n){this.columnCount=t,this.errorCorrectionLevel=n,this.rowCountUpperPart=e,this.rowCountLowerPart=r,this.rowCount=e+r}getColumnCount(){return this.columnCount}getErrorCorrectionLevel(){return this.errorCorrectionLevel}getRowCount(){return this.rowCount}getRowCountUpperPart(){return this.rowCountUpperPart}getRowCountLowerPart(){return this.rowCountLowerPart}}class Ue{constructor(){this.buffer=""}static form(t,e){let r=-1;return t.replace(/%(-)?(0?[0-9]+)?([.][0-9]+)?([#][0-9]+)?([scfpexd%])/g,(function(t,n,i,o,s,a){if("%%"===t)return"%";if(void 0===e[++r])return;t=o?parseInt(o.substr(1)):void 0;let l,c=s?parseInt(s.substr(1)):void 0;switch(a){case"s":l=e[r];break;case"c":l=e[r][0];break;case"f":l=parseFloat(e[r]).toFixed(t);break;case"p":l=parseFloat(e[r]).toPrecision(t);break;case"e":l=parseFloat(e[r]).toExponential(t);break;case"x":l=parseInt(e[r]).toString(c||16);break;case"d":l=parseFloat(parseInt(e[r],c||10).toPrecision(t)).toFixed(0)}l="object"==typeof l?JSON.stringify(l):(+l).toString(c);let h=parseInt(i),u=i&&i[0]+""=="0"?"0":" ";for(;l.length=0&&(e=this.codewords[n],null!=e))return e;if(n=this.imageRowToCodewordIndex(t)+r,nr,getValue:()=>n};i.getValue()>t?(t=i.getValue(),e=[],e.push(i.getKey())):i.getValue()===t&&e.push(i.getKey())}return be.toIntArray(e)}getConfidence(t){return this.values.get(t)}}class ze extends He{constructor(t,e){super(t),this._isLeft=e}setRowNumbers(){for(let t of this.getCodewords())null!=t&&t.setRowNumberAsRowIndicatorColumn()}adjustCompleteIndicatorColumnRowNumbers(t){let e=this.getCodewords();this.setRowNumbers(),this.removeIncorrectCodewords(e,t);let r=this.getBoundingBox(),n=this._isLeft?r.getTopLeft():r.getTopRight(),i=this._isLeft?r.getBottomLeft():r.getBottomRight(),o=this.imageRowToCodewordIndex(Math.trunc(n.getY())),s=this.imageRowToCodewordIndex(Math.trunc(i.getY())),a=-1,l=1,c=0;for(let r=o;r=t.getRowCount()||i>r)e[r]=null;else{let t;t=l>2?(l-2)*i:i;let o=t>=r;for(let n=1;n<=t&&!o;n++)o=null!=e[r-n];o?e[r]=null:(a=n.getRowNumber(),c=1)}}}getRowHeights(){let t=this.getBarcodeMetadata();if(null==t)return null;this.adjustIncompleteIndicatorColumnRowNumbers(t);let e=new Int32Array(t.getRowCount());for(let t of this.getCodewords())if(null!=t){let r=t.getRowNumber();if(r>=e.length)continue;e[r]++}return e}adjustIncompleteIndicatorColumnRowNumbers(t){let e=this.getBoundingBox(),r=this._isLeft?e.getTopLeft():e.getTopRight(),n=this._isLeft?e.getBottomLeft():e.getBottomRight(),i=this.imageRowToCodewordIndex(Math.trunc(r.getY())),o=this.imageRowToCodewordIndex(Math.trunc(n.getY())),s=this.getCodewords(),a=-1;for(let e=i;e=t.getRowCount()?s[e]=null:a=r.getRowNumber())}}getBarcodeMetadata(){let t=this.getCodewords(),e=new Ve,r=new Ve,n=new Ve,i=new Ve;for(let o of t){if(null==o)continue;o.setRowNumberAsRowIndicatorColumn();let t=o.getValue()%30,s=o.getRowNumber();switch(this._isLeft||(s+=2),s%3){case 0:r.setValue(3*t+1);break;case 1:i.setValue(t/3),n.setValue(t%3);break;case 2:e.setValue(t+1)}}if(0===e.getValue().length||0===r.getValue().length||0===n.getValue().length||0===i.getValue().length||e.getValue()[0]<1||r.getValue()[0]+n.getValue()[0]be.MAX_ROWS_IN_BARCODE)return null;let o=new ke(e.getValue()[0],r.getValue()[0],n.getValue()[0],i.getValue()[0]);return this.removeIncorrectCodewords(t,o),o}removeIncorrectCodewords(t,e){for(let r=0;re.getRowCount())t[r]=null;else switch(this._isLeft||(o+=2),o%3){case 0:3*i+1!==e.getRowCountUpperPart()&&(t[r]=null);break;case 1:Math.trunc(i/3)===e.getErrorCorrectionLevel()&&i%3===e.getRowCountLowerPart()||(t[r]=null);break;case 2:i+1!==e.getColumnCount()&&(t[r]=null)}}}isLeft(){return this._isLeft}toString(){return"IsLeft: "+this._isLeft+"\n"+super.toString()}}class Ge{constructor(t,e){this.ADJUST_ROW_NUMBER_SKIP=2,this.barcodeMetadata=t,this.barcodeColumnCount=t.getColumnCount(),this.boundingBox=e,this.detectionResultColumns=new Array(this.barcodeColumnCount+2)}getDetectionResultColumns(){this.adjustIndicatorColumnRowNumbers(this.detectionResultColumns[0]),this.adjustIndicatorColumnRowNumbers(this.detectionResultColumns[this.barcodeColumnCount+1]);let t,e=be.MAX_CODEWORDS_IN_BARCODE;do{t=e,e=this.adjustRowNumbersAndGetCount()}while(e>0&&e0&&i0&&(s[0]=r[e-1],s[4]=i[e-1],s[5]=o[e-1]),e>1&&(s[8]=r[e-2],s[10]=i[e-2],s[11]=o[e-2]),e>=1;r=1&e,Xe.RATIOS_TABLE[t]||(Xe.RATIOS_TABLE[t]=new Array(be.BARS_IN_MODULE)),Xe.RATIOS_TABLE[t][be.BARS_IN_MODULE-n-1]=Math.fround(i/be.MODULES_IN_CODEWORD)}}this.bSymbolTableReady=!0}static getDecodedValue(t){let e=Xe.getDecodedCodewordValue(Xe.sampleBitCounts(t));return-1!==e?e:Xe.getClosestDecodedValue(t)}static sampleBitCounts(t){let e=et.sum(t),r=new Int32Array(be.BARS_IN_MODULE),n=0,i=0;for(let o=0;o1)for(let n=0;n=n)break}enew Array(be.BARS_IN_MODULE)));class We{constructor(){this.segmentCount=-1,this.fileSize=-1,this.timestamp=-1,this.checksum=-1}getSegmentIndex(){return this.segmentIndex}setSegmentIndex(t){this.segmentIndex=t}getFileId(){return this.fileId}setFileId(t){this.fileId=t}getOptionalData(){return this.optionalData}setOptionalData(t){this.optionalData=t}isLastSegment(){return this.lastSegment}setLastSegment(t){this.lastSegment=t}getSegmentCount(){return this.segmentCount}setSegmentCount(t){this.segmentCount=t}getSender(){return this.sender||null}setSender(t){this.sender=t}getAddressee(){return this.addressee||null}setAddressee(t){this.addressee=t}getFileName(){return this.fileName}setFileName(t){this.fileName=t}getFileSize(){return this.fileSize}setFileSize(t){this.fileSize=t}getChecksum(){return this.checksum}setChecksum(t){this.checksum=t}getTimestamp(){return this.timestamp}setTimestamp(t){this.timestamp=t}}class je{static parseLong(t,e){return parseInt(t,e)}}class Ze extends o{}Ze.kind="NullPointerException";class Qe extends o{}class Ke extends class{writeBytes(t){this.writeBytesOffset(t,0,t.length)}writeBytesOffset(t,e,r){if(null==t)throw new Ze;if(e<0||e>t.length||r<0||e+r>t.length||e+r<0)throw new d;if(0!==r)for(let n=0;n0&&this.grow(t)}grow(t){let e=this.buf.length<<1;if(e-t<0&&(e=t),e<0){if(t<0)throw new Qe;e=w.MAX_VALUE}this.buf=f.copyOfUint8Array(this.buf,e)}write(t){this.ensureCapacity(this.count+1),this.buf[this.count]=t,this.count+=1}writeBytesOffset(t,e,r){if(e<0||e>t.length||r<0||e+r-t.length>0)throw new d;this.ensureCapacity(this.count+r),u.arraycopy(t,e,this.buf,this.count,r),this.count+=r}writeTo(t){t.writeBytesOffset(this.buf,0,this.count)}reset(){this.count=0}toByteArray(){return f.copyOfUint8Array(this.buf,this.count)}size(){return this.count}toString(t){return t?"string"==typeof t?this.toString_string(t):this.toString_number(t):this.toString_void()}toString_void(){return new String(this.buf).toString()}toString_string(t){return new String(this.buf).toString()}toString_number(t){return new String(this.buf).toString()}close(){}}function qe(){if("undefined"!=typeof window)return window.BigInt||null;if(void 0!==r.g)return r.g.BigInt||null;if("undefined"!=typeof self)return self.BigInt||null;throw new Error("Can't search globals for BigInt!")}let Je;function $e(t){if(void 0===Je&&(Je=qe()),null===Je)throw new Error("BigInt is not supported!");return Je(t)}!function(t){t[t.ALPHA=0]="ALPHA",t[t.LOWER=1]="LOWER",t[t.MIXED=2]="MIXED",t[t.PUNCT=3]="PUNCT",t[t.ALPHA_SHIFT=4]="ALPHA_SHIFT",t[t.PUNCT_SHIFT=5]="PUNCT_SHIFT"}(Y||(Y={}));class tr{static decode(t,e){let r=new T(""),n=I.ISO8859_1;r.enableDecoding(n);let i=1,o=t[i++],s=new We;for(;it[0])throw C.getFormatInstance();let n=new Int32Array(tr.NUMBER_OF_SEQUENCE_CODEWORDS);for(let r=0;r0){for(let t=0;t<6;++t)o.write(Number($e(a)>>$e(8*(5-t))));a=0,s=0}}n===e[0]&&r0){for(let t=0;t<6;++t)o.write(Number($e(a)>>$e(8*(5-t))));a=0,s=0}}}return i.append(p.decode(o.toByteArray(),r)),n}static numericCompaction(t,e,r){let n=0,i=!1,o=new Int32Array(tr.MAX_NUMERIC_CODEWORDS);for(;e0&&(r.append(tr.decodeBase900toBase10(o,n)),n=0)}return e}static decodeBase900toBase10(t,e){let r=$e(0);for(let n=0;n@[\\]_`~!\r\t,:\n-.$/\"|*()?{}'",tr.MIXED_CHARS="0123456789&\r\t,:#-.$/+%*=^",tr.EXP900=qe()?function(){let t=[];t[0]=$e(1);let e=$e(900);t[1]=e;for(let r=2;r<16;r++)t[r]=t[r-1]*e;return t}():[],tr.NUMBER_OF_SEQUENCE_CODEWORDS=2;class er{constructor(){}static decode(t,e,r,n,i,o,s){let a,l=new xe(t,e,r,n,i),c=null,h=null;for(let r=!0;;r=!1){if(null!=e&&(c=er.getRowIndicatorColumn(t,l,e,!0,o,s)),null!=n&&(h=er.getRowIndicatorColumn(t,l,n,!1,o,s)),a=er.merge(c,h),null==a)throw y.getNotFoundInstance();let i=a.getBoundingBox();if(!r||null==i||!(i.getMinY()l.getMaxY()))break;l=i}a.setBoundingBox(l);let u=a.getBarcodeColumnCount()+1;a.setDetectionResultColumn(0,c),a.setDetectionResultColumn(u,h);let d=null!=c;for(let e=1;e<=u;e++){let r,n=d?e:u-e;if(void 0!==a.getDetectionResultColumn(n))continue;r=0===n||n===u?new ze(l,0===n):new He(l),a.setDetectionResultColumn(n,r);let i=-1,c=i;for(let e=l.getMinY();e<=l.getMaxY();e++){if(i=er.getStartColumn(a,n,e,d),i<0||i>l.getMaxX()){if(-1===c)continue;i=c}let h=er.detectCodeword(t,l.getMinX(),l.getMaxX(),d,i,e,o,s);null!=h&&(r.setCodeword(e,h),c=i,o=Math.min(o,h.getWidth()),s=Math.max(s,h.getWidth()))}}return er.createDecoderResult(a)}static merge(t,e){if(null==t&&null==e)return null;let r=er.getBarcodeMetadata(t,e);if(null==r)return null;let n=xe.merge(er.adjustBoundingBox(t),er.adjustBoundingBox(e));return new Ge(r,n)}static adjustBoundingBox(t){if(null==t)return null;let e=t.getRowHeights();if(null==e)return null;let r=er.getMax(e),n=0;for(let t of e)if(n+=r-t,t>0)break;let i=t.getCodewords();for(let t=0;n>0&&null==i[t];t++)n--;let o=0;for(let t=e.length-1;t>=0&&(o+=r-e[t],!(e[t]>0));t--);for(let t=i.length-1;o>0&&null==i[t];t--)o--;return t.getBoundingBox().addMissingRows(n,o,t.isLeft())}static getMax(t){let e=-1;for(let r of t)e=Math.max(e,r);return e}static getBarcodeMetadata(t,e){let r,n;return null==t||null==(r=t.getBarcodeMetadata())?null==e?null:e.getBarcodeMetadata():null==e||null==(n=e.getBarcodeMetadata())?r:r.getColumnCount()!==n.getColumnCount()&&r.getErrorCorrectionLevel()!==n.getErrorCorrectionLevel()&&r.getRowCount()!==n.getRowCount()?null:r}static getRowIndicatorColumn(t,e,r,n,i,o){let s=new ze(e,n);for(let a=0;a<2;a++){let l=0===a?1:-1,c=Math.trunc(Math.trunc(r.getX()));for(let a=Math.trunc(Math.trunc(r.getY()));a<=e.getMaxY()&&a>=e.getMinY();a+=l){let e=er.detectCodeword(t,0,t.getWidth(),n,c,a,i,o);null!=e&&(s.setCodeword(a,e),c=n?e.getStartX():e.getEndX())}}return s}static adjustCodewordCount(t,e){let r=e[0][1],n=r.getValue(),i=t.getBarcodeColumnCount()*t.getBarcodeRowCount()-er.getNumberOfECCodeWords(t.getBarcodeECLevel());if(0===n.length){if(i<1||i>be.MAX_CODEWORDS_IN_BARCODE)throw y.getNotFoundInstance();r.setValue(i)}else n[0]!==i&&r.setValue(i)}static createDecoderResult(t){let e=er.createBarcodeMatrix(t);er.adjustCodewordCount(t,e);let r=new Array,n=new Int32Array(t.getBarcodeRowCount()*t.getBarcodeColumnCount()),i=[],o=new Array;for(let s=0;s0;){for(let t=0;tnew Array(t.getBarcodeColumnCount()+2)));for(let t=0;t=0){if(n>=e.length)continue;e[n][r].setValue(t.getValue())}}r++}return e}static isValidBarcodeColumn(t,e){return e>=0&&e<=t.getBarcodeColumnCount()+1}static getStartColumn(t,e,r,n){let i=n?1:-1,o=null;if(er.isValidBarcodeColumn(t,e-i)&&(o=t.getDetectionResultColumn(e-i).getCodeword(r)),null!=o)return n?o.getEndX():o.getStartX();if(o=t.getDetectionResultColumn(e).getCodewordNearby(r),null!=o)return n?o.getStartX():o.getEndX();if(er.isValidBarcodeColumn(t,e-i)&&(o=t.getDetectionResultColumn(e-i).getCodewordNearby(r)),null!=o)return n?o.getEndX():o.getStartX();let s=0;for(;er.isValidBarcodeColumn(t,e-i);){e-=i;for(let r of t.getDetectionResultColumn(e).getCodewords())if(null!=r)return(n?r.getEndX():r.getStartX())+i*s*(r.getEndX()-r.getStartX());s++}return n?t.getBoundingBox().getMinX():t.getBoundingBox().getMaxX()}static detectCodeword(t,e,r,n,i,o,s,a){i=er.adjustCodewordStartColumn(t,e,r,n,i,o);let l,c=er.getModuleBitCount(t,e,r,n,i,o);if(null==c)return null;let h=et.sum(c);if(n)l=i+h;else{for(let t=0;t=e)&&l=e:ser.CODEWORD_SKEW_SIZE)return i;s+=a}a=-a,n=!n}return s}static checkCodewordSkew(t,e,r){return e-er.CODEWORD_SKEW_SIZE<=t&&t<=r+er.CODEWORD_SKEW_SIZE}static decodeCodewords(t,e,r){if(0===t.length)throw C.getFormatInstance();let n=1<r/2+er.MAX_ERRORS||r<0||r>er.MAX_EC_CODEWORDS)throw c.getChecksumInstance();return er.errorCorrection.decode(t,r,e)}static verifyCodewordCount(t,e){if(t.length<4)throw C.getFormatInstance();let r=t[0];if(r>t.length)throw C.getFormatInstance();if(0===r){if(!(e>=1;return e}static getCodewordBucketNumber(t){return t instanceof Int32Array?this.getCodewordBucketNumber_Int32Array(t):this.getCodewordBucketNumber_number(t)}static getCodewordBucketNumber_number(t){return er.getCodewordBucketNumber(er.getBitCountForCodeword(t))}static getCodewordBucketNumber_Int32Array(t){return(t[0]-t[2]+t[4]-t[6]+9)%9}static toString(t){let e=new Ue;for(let r=0;rt))}static getMaxWidth(t,e){return null==t||null==e?0:Math.trunc(Math.abs(t.getX()-e.getX()))}static getMinWidth(t,e){return null==t||null==e?w.MAX_VALUE:Math.trunc(Math.abs(t.getX()-e.getX()))}static getMaxCodewordWidth(t){return Math.floor(Math.max(Math.max(rr.getMaxWidth(t[0],t[4]),rr.getMaxWidth(t[6],t[2])*be.MODULES_IN_CODEWORD/be.MODULES_IN_STOP_PATTERN),Math.max(rr.getMaxWidth(t[1],t[5]),rr.getMaxWidth(t[7],t[3])*be.MODULES_IN_CODEWORD/be.MODULES_IN_STOP_PATTERN)))}static getMinCodewordWidth(t){return Math.floor(Math.min(Math.min(rr.getMinWidth(t[0],t[4]),rr.getMinWidth(t[6],t[2])*be.MODULES_IN_CODEWORD/be.MODULES_IN_STOP_PATTERN),Math.min(rr.getMinWidth(t[1],t[5]),rr.getMinWidth(t[7],t[3])*be.MODULES_IN_CODEWORD/be.MODULES_IN_STOP_PATTERN)))}reset(){}}class nr extends o{}nr.kind="ReaderException";class ir{constructor(t,e){this.verbose=!0===t,e&&this.setHints(e)}decode(t,e){return e&&this.setHints(e),this.decodeInternal(t)}decodeWithState(t){return null!==this.readers&&void 0!==this.readers||this.setHints(null),this.decodeInternal(t)}setHints(t){this.hints=t;const e=null!=t&&void 0!==t.get(E.TRY_HARDER),r=null==t?null:t.get(E.POSSIBLE_FORMATS),n=new Array;if(null!=r){const i=r.some((t=>t===k.UPC_A||t===k.UPC_E||t===k.EAN_13||t===k.EAN_8||t===k.CODABAR||t===k.CODE_39||t===k.CODE_93||t===k.CODE_128||t===k.ITF||t===k.RSS_14||t===k.RSS_EXPANDED));i&&!e&&n.push(new ee(t,this.verbose)),r.includes(k.QR_CODE)&&n.push(new Oe),r.includes(k.DATA_MATRIX)&&n.push(new ue),r.includes(k.AZTEC)&&n.push(new gt),r.includes(k.PDF_417)&&n.push(new rr),i&&e&&n.push(new ee(t,this.verbose))}0===n.length&&(e||n.push(new ee(t,this.verbose)),n.push(new Oe),n.push(new ue),n.push(new gt),n.push(new rr),e&&n.push(new ee(t,this.verbose))),this.readers=n}reset(){if(null!==this.readers)for(const t of this.readers)t.reset()}decodeInternal(t){if(null===this.readers)throw new nr("No readers where selected, nothing can be read.");for(const e of this.readers)try{return e.decode(t,this.hints)}catch(t){if(t instanceof nr)continue}throw new y("No MultiFormat Readers were able to detect the code.")}}var or;!function(t){t[t.ERROR_CORRECTION=0]="ERROR_CORRECTION",t[t.CHARACTER_SET=1]="CHARACTER_SET",t[t.DATA_MATRIX_SHAPE=2]="DATA_MATRIX_SHAPE",t[t.MIN_SIZE=3]="MIN_SIZE",t[t.MAX_SIZE=4]="MAX_SIZE",t[t.MARGIN=5]="MARGIN",t[t.PDF417_COMPACT=6]="PDF417_COMPACT",t[t.PDF417_COMPACTION=7]="PDF417_COMPACTION",t[t.PDF417_DIMENSIONS=8]="PDF417_DIMENSIONS",t[t.AZTEC_LAYERS=9]="AZTEC_LAYERS",t[t.QR_VERSION=10]="QR_VERSION"}(or||(or={}));var sr=or;class ar{constructor(t){this.field=t,this.cachedGenerators=[],this.cachedGenerators.push(new Z(t,Int32Array.from([1])))}buildGenerator(t){const e=this.cachedGenerators;if(t>=e.length){let r=e[e.length-1];const n=this.field;for(let i=e.length;i<=t;i++){const t=r.multiply(new Z(n,Int32Array.from([1,n.exp(i-1+n.getGeneratorBase())])));e.push(t),r=t}}return e[t]}encode(t,e){if(0===e)throw new a("No error correction bytes");const r=t.length-e;if(r<=0)throw new a("No data bytes provided");const n=this.buildGenerator(e),i=new Int32Array(r);u.arraycopy(t,0,i,0,r);let o=new Z(this.field,i);o=o.multiplyByMonomial(e,1);const s=o.divide(n)[1].getCoefficients(),l=e-s.length;for(let e=0;e=5&&(r+=lr.N1+(n-5)),n=1,s=i)}n>=5&&(r+=lr.N1+(n-5))}return r}}lr.N1=3,lr.N2=3,lr.N3=40,lr.N4=10;class cr{constructor(t,e){this.width=t,this.height=e;const r=new Array(e);for(let n=0;n!==e;n++)r[n]=new Uint8Array(t);this.bytes=r}getHeight(){return this.height}getWidth(){return this.width}get(t,e){return this.bytes[e][t]}getArray(){return this.bytes}setNumber(t,e,r){this.bytes[e][t]=r}setBoolean(t,e,r){this.bytes[e][t]=r?1:0}clear(t){for(const e of this.bytes)f.fill(e,t)}equals(t){if(!(t instanceof cr))return!1;const e=t;if(this.width!==e.width)return!1;if(this.height!==e.height)return!1;for(let t=0,r=this.height;t>\n"),t.toString()}setMode(t){this.mode=t}setECLevel(t){this.ecLevel=t}setVersion(t){this.version=t}setMaskPattern(t){this.maskPattern=t}setMatrix(t){this.matrix=t}static isValidMaskPattern(t){return t>=0&&t0;){for(6===o&&(o-=1);s>=0&&s=r;)t^=e<=0)for(let t=0;t!==r;t++){const r=n[t];r>=0&&dr.isEmpty(e.get(r,i))&&dr.embedPositionAdjustmentPattern(r-2,i-2,e)}}}}dr.POSITION_DETECTION_PATTERN=Array.from([Int32Array.from([1,1,1,1,1,1,1]),Int32Array.from([1,0,0,0,0,0,1]),Int32Array.from([1,0,1,1,1,0,1]),Int32Array.from([1,0,1,1,1,0,1]),Int32Array.from([1,0,1,1,1,0,1]),Int32Array.from([1,0,0,0,0,0,1]),Int32Array.from([1,1,1,1,1,1,1])]),dr.POSITION_ADJUSTMENT_PATTERN=Array.from([Int32Array.from([1,1,1,1,1]),Int32Array.from([1,0,0,0,1]),Int32Array.from([1,0,1,0,1]),Int32Array.from([1,0,0,0,1]),Int32Array.from([1,1,1,1,1])]),dr.POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE=Array.from([Int32Array.from([-1,-1,-1,-1,-1,-1,-1]),Int32Array.from([6,18,-1,-1,-1,-1,-1]),Int32Array.from([6,22,-1,-1,-1,-1,-1]),Int32Array.from([6,26,-1,-1,-1,-1,-1]),Int32Array.from([6,30,-1,-1,-1,-1,-1]),Int32Array.from([6,34,-1,-1,-1,-1,-1]),Int32Array.from([6,22,38,-1,-1,-1,-1]),Int32Array.from([6,24,42,-1,-1,-1,-1]),Int32Array.from([6,26,46,-1,-1,-1,-1]),Int32Array.from([6,28,50,-1,-1,-1,-1]),Int32Array.from([6,30,54,-1,-1,-1,-1]),Int32Array.from([6,32,58,-1,-1,-1,-1]),Int32Array.from([6,34,62,-1,-1,-1,-1]),Int32Array.from([6,26,46,66,-1,-1,-1]),Int32Array.from([6,26,48,70,-1,-1,-1]),Int32Array.from([6,26,50,74,-1,-1,-1]),Int32Array.from([6,30,54,78,-1,-1,-1]),Int32Array.from([6,30,56,82,-1,-1,-1]),Int32Array.from([6,30,58,86,-1,-1,-1]),Int32Array.from([6,34,62,90,-1,-1,-1]),Int32Array.from([6,28,50,72,94,-1,-1]),Int32Array.from([6,26,50,74,98,-1,-1]),Int32Array.from([6,30,54,78,102,-1,-1]),Int32Array.from([6,28,54,80,106,-1,-1]),Int32Array.from([6,32,58,84,110,-1,-1]),Int32Array.from([6,30,58,86,114,-1,-1]),Int32Array.from([6,34,62,90,118,-1,-1]),Int32Array.from([6,26,50,74,98,122,-1]),Int32Array.from([6,30,54,78,102,126,-1]),Int32Array.from([6,26,52,78,104,130,-1]),Int32Array.from([6,30,56,82,108,134,-1]),Int32Array.from([6,34,60,86,112,138,-1]),Int32Array.from([6,30,58,86,114,142,-1]),Int32Array.from([6,34,62,90,118,146,-1]),Int32Array.from([6,30,54,78,102,126,150]),Int32Array.from([6,24,50,76,102,128,154]),Int32Array.from([6,28,54,80,106,132,158]),Int32Array.from([6,32,58,84,110,136,162]),Int32Array.from([6,26,54,82,110,138,166]),Int32Array.from([6,30,58,86,114,142,170])]),dr.TYPE_INFO_COORDINATES=Array.from([Int32Array.from([8,0]),Int32Array.from([8,1]),Int32Array.from([8,2]),Int32Array.from([8,3]),Int32Array.from([8,4]),Int32Array.from([8,5]),Int32Array.from([8,7]),Int32Array.from([8,8]),Int32Array.from([7,8]),Int32Array.from([5,8]),Int32Array.from([4,8]),Int32Array.from([3,8]),Int32Array.from([2,8]),Int32Array.from([1,8]),Int32Array.from([0,8])]),dr.VERSION_INFO_POLY=7973,dr.TYPE_INFO_POLY=1335,dr.TYPE_INFO_MASK_PATTERN=21522;class gr{constructor(t,e){this.dataBytes=t,this.errorCorrectionBytes=e}getDataBytes(){return this.dataBytes}getErrorCorrectionBytes(){return this.errorCorrectionBytes}}class fr{constructor(){}static calculateMaskPenalty(t){return lr.applyMaskPenaltyRule1(t)+lr.applyMaskPenaltyRule2(t)+lr.applyMaskPenaltyRule3(t)+lr.applyMaskPenaltyRule4(t)}static encode(t,e,r=null){let n=fr.DEFAULT_BYTE_MODE_ENCODING;const i=null!==r&&void 0!==r.get(sr.CHARACTER_SET);i&&(n=r.get(sr.CHARACTER_SET).toString());const o=this.chooseMode(t,n),s=new A;if(o===Ie.BYTE&&(i||fr.DEFAULT_BYTE_MODE_ENCODING!==n)){const t=I.getCharacterSetECIByName(n);void 0!==t&&this.appendECI(t,s)}this.appendModeInfo(o,s);const a=new A;let l;if(this.appendBytes(t,o,a,n),null!==r&&void 0!==r.get(sr.QR_VERSION)){const t=Number.parseInt(r.get(sr.QR_VERSION).toString(),10);l=Ae.getVersionForNumber(t);const n=this.calculateBitsNeeded(o,s,a,l);if(!this.willFit(n,l,e))throw new ur("Data too big for requested version")}else l=this.recommendVersion(e,o,s,a);const c=new A;c.appendBitArray(s);const h=o===Ie.BYTE?a.getSizeInBytes():t.length;this.appendLengthInfo(h,l,o,c),c.appendBitArray(a);const u=l.getECBlocksForLevel(e),d=l.getTotalCodewords()-u.getTotalECCodewords();this.terminateBits(d,c);const g=this.interleaveWithECBytes(c,l.getTotalCodewords(),d,u.getNumBlocks()),f=new hr;f.setECLevel(e),f.setMode(o),f.setVersion(l);const w=l.getDimensionForVersion(),m=new cr(w,w),E=this.chooseMaskPattern(g,e,l,m);return f.setMaskPattern(E),dr.buildMatrix(g,e,l,E,m),f.setMatrix(m),f}static recommendVersion(t,e,r,n){const i=this.calculateBitsNeeded(e,r,n,Ae.getVersionForNumber(1)),o=this.chooseVersion(i,t),s=this.calculateBitsNeeded(e,r,n,o);return this.chooseVersion(s,t)}static calculateBitsNeeded(t,e,r,n){return e.getSize()+t.getCharacterCountBits(n)+r.getSize()}static getAlphanumericCode(t){return t159)&&(r<224||r>235))return!1}return!0}static chooseMaskPattern(t,e,r,n){let i=Number.MAX_SAFE_INTEGER,o=-1;for(let s=0;s=(t+7)/8}static terminateBits(t,e){const r=8*t;if(e.getSize()>r)throw new ur("data bits cannot fit in the QR Code"+e.getSize()+" > "+r);for(let t=0;t<4&&e.getSize()0)for(let t=n;t<8;t++)e.appendBit(!1);const i=t-e.getSizeInBytes();for(let t=0;t=r)throw new ur("Block ID too large");const s=t%r,a=r-s,l=Math.floor(t/r),c=l+1,h=Math.floor(e/r),u=h+1,d=l-h,g=c-u;if(d!==g)throw new ur("EC bytes mismatch");if(r!==a+s)throw new ur("RS blocks mismatch");if(t!==(h+d)*a+(u+g)*s)throw new ur("Total bytes mismatch");n=1<=0&&e<=9}static appendNumericBytes(t,e){const r=t.length;let n=0;for(;n=33088&&n<=40956?i=n-33088:n>=57408&&n<=60351&&(i=n-49472),-1===i)throw new ur("Invalid byte sequence");const o=192*(i>>8)+(255&i);e.appendBits(o,13)}}static appendECI(t,e){e.appendBits(Ie.ECI.getBits(),4),e.appendBits(t.getValue(),8)}}fr.ALPHANUMERIC_TABLE=Int32Array.from([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,36,-1,-1,-1,37,38,-1,-1,-1,-1,39,40,-1,41,42,43,0,1,2,3,4,5,6,7,8,9,44,-1,-1,-1,-1,-1,-1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,-1,-1,-1,-1,-1]),fr.DEFAULT_BYTE_MODE_ENCODING=I.UTF8.getName();class wr{write(t,e,r,n=null){if(0===t.length)throw new a("Found empty contents");if(e<0||r<0)throw new a("Requested dimensions are too small: "+e+"x"+r);let i=de.L,o=wr.QUIET_ZONE_SIZE;null!==n&&(void 0!==n.get(sr.ERROR_CORRECTION)&&(i=de.fromString(n.get(sr.ERROR_CORRECTION).toString())),void 0!==n.get(sr.MARGIN)&&(o=Number.parseInt(n.get(sr.MARGIN).toString(),10)));const s=fr.encode(t,i,n);return this.renderResult(s,e,r,o)}writeToDom(t,e,r,n,i=null){"string"==typeof t&&(t=document.querySelector(t));const o=this.write(e,r,n,i);t&&t.appendChild(o)}renderResult(t,e,r,n){const i=t.getMatrix();if(null===i)throw new J;const o=i.getWidth(),s=i.getHeight(),a=o+2*n,l=s+2*n,c=Math.max(e,a),h=Math.max(r,l),u=Math.min(Math.floor(c/a),Math.floor(h/l)),d=Math.floor((c-o*u)/2),g=Math.floor((h-s*u)/2),f=this.createSVGElement(c,h);for(let t=0,e=g;te||i+s>r)throw new a("Crop rectangle does not fit within image data.");l&&this.reverseHorizontal(o,s)}getRow(t,e){if(t<0||t>=this.getHeight())throw new a("Requested row is outside the image: "+t);const r=this.getWidth();(null==e||e.length>16&255,o=r>>7&510,s=255&r;i[e]=(n+o+s)/4&255}this.luminances=i}else this.luminances=t;if(void 0===n&&(this.dataWidth=e),void 0===i&&(this.dataHeight=r),void 0===o&&(this.left=0),void 0===s&&(this.top=0),this.left+e>this.dataWidth||this.top+r>this.dataHeight)throw new a("Crop rectangle does not fit within image data.")}getRow(t,e){if(t<0||t>=this.getHeight())throw new a("Requested row is outside the image: "+t);const r=this.getWidth();(null==e||e.length"}}class Tr extends Sr{constructor(t,e,r){super(t,0,0),this.binaryShiftStart=e,this.binaryShiftByteCount=r}appendTo(t,e){for(let r=0;r62?t.appendBits(this.binaryShiftByteCount-31,16):0===r?t.appendBits(Math.min(this.binaryShiftByteCount,31),5):t.appendBits(this.binaryShiftByteCount-31,5)),t.appendBits(e[this.binaryShiftStart+r],8)}addBinaryShift(t,e){return new Tr(this,t,e)}toString(){return"<"+this.binaryShiftStart+"::"+(this.binaryShiftStart+this.binaryShiftByteCount-1)+">"}}function Nr(t,e,r){return new Sr(t,e,r)}const yr=["UPPER","LOWER","DIGIT","MIXED","PUNCT"],Mr=new Sr(null,0,0),Dr=[Int32Array.from([0,327708,327710,327709,656318]),Int32Array.from([590318,0,327710,327709,656318]),Int32Array.from([262158,590300,0,590301,932798]),Int32Array.from([327709,327708,656318,0,327710]),Int32Array.from([327711,656380,656382,656381,0])];const Rr=function(t){for(let e of t)f.fill(e,-1);return t[0][4]=0,t[1][4]=0,t[1][0]=28,t[3][4]=0,t[2][4]=0,t[2][0]=15,t}(f.createInt32Array(6,6));class Or{constructor(t,e,r,n){this.token=t,this.mode=e,this.binaryShiftByteCount=r,this.bitCount=n}getMode(){return this.mode}getToken(){return this.token}getBinaryShiftByteCount(){return this.binaryShiftByteCount}getBitCount(){return this.bitCount}latchAndAppend(t,e){let r=this.bitCount,n=this.token;if(t!==this.mode){let e=Dr[this.mode][t];n=Nr(n,65535&e,e>>16),r+=e>>16}let i=2===t?4:5;return n=Nr(n,e,i),new Or(n,t,0,r+i)}shiftAndAppend(t,e){let r=this.token,n=2===this.mode?4:5;return r=Nr(r,Rr[this.mode][t],n),r=Nr(r,e,5),new Or(r,this.mode,0,this.bitCount+n+5)}addBinaryShiftChar(t){let e=this.token,r=this.mode,n=this.bitCount;if(4===this.mode||2===this.mode){let t=Dr[r][0];e=Nr(e,65535&t,t>>16),n+=t>>16,r=0}let i=0===this.binaryShiftByteCount||31===this.binaryShiftByteCount?18:62===this.binaryShiftByteCount?9:8,o=new Or(e,r,this.binaryShiftByteCount+1,n+i);return 2078===o.binaryShiftByteCount&&(o=o.endBinaryShift(t+1)),o}endBinaryShift(t){if(0===this.binaryShiftByteCount)return this;let e=this.token;return e=function(t,e,r){return new Tr(t,e,r)}(e,t-this.binaryShiftByteCount,this.binaryShiftByteCount),new Or(e,this.mode,0,this.bitCount)}isBetterThanOrEqualTo(t){let e=this.bitCount+(Dr[this.mode][t.mode]>>16);return this.binaryShiftByteCountt.binaryShiftByteCount&&t.binaryShiftByteCount>0&&(e+=10),e<=t.bitCount}toBitArray(t){let e=[];for(let r=this.endBinaryShift(t.length).token;null!==r;r=r.getPrevious())e.unshift(r);let r=new A;for(const n of e)n.appendTo(r,t);return r}toString(){return S.format("%s bits=%d bytes=%d",yr[this.mode],this.bitCount,this.binaryShiftByteCount)}static calculateBinaryShiftCost(t){return t.binaryShiftByteCount>62?21:t.binaryShiftByteCount>31?20:t.binaryShiftByteCount>0?10:0}}Or.INITIAL_STATE=new Or(Mr,0,0,0);const br=function(t){const e=S.getCharCode(" "),r=S.getCharCode("."),n=S.getCharCode(",");t[0][e]=1;const i=S.getCharCode("Z"),o=S.getCharCode("A");for(let e=o;e<=i;e++)t[0][e]=e-o+2;t[1][e]=1;const s=S.getCharCode("z"),a=S.getCharCode("a");for(let e=a;e<=s;e++)t[1][e]=e-a+2;t[2][e]=1;const l=S.getCharCode("9"),c=S.getCharCode("0");for(let e=c;e<=l;e++)t[2][e]=e-c+2;t[2][n]=12,t[2][r]=13;const h=["\0"," ","","","","","","","","\b","\t","\n","\v","\f","\r","","","","","","@","\\","^","_","`","|","~",""];for(let e=0;e","?","[","]","{","}"];for(let e=0;e0&&(t[4][S.getCharCode(u[e])]=e);return t}(f.createInt32Array(5,256));class Lr{constructor(t){this.text=t}encode(){const t=S.getCharCode(" "),e=S.getCharCode("\n");let r=pr.singletonList(Or.INITIAL_STATE);for(let n=0;n0?(r=Lr.updateStateListForPair(r,n,i),n++):r=this.updateStateListForChar(r,n)}return pr.min(r,((t,e)=>t.getBitCount()-e.getBitCount())).toBitArray(this.text)}updateStateListForChar(t,e){const r=[];for(let n of t)this.updateStateForChar(n,e,r);return Lr.simplifyStates(r)}updateStateForChar(t,e,r){let n=255&this.text[e],i=br[t.getMode()][n]>0,o=null;for(let s=0;s<=4;s++){let a=br[s][n];if(a>0){if(null==o&&(o=t.endBinaryShift(e)),!i||s===t.getMode()||2===s){const t=o.latchAndAppend(s,a);r.push(t)}if(!i&&Rr[t.getMode()][s]>=0){const t=o.shiftAndAppend(s,a);r.push(t)}}}if(t.getBinaryShiftByteCount()>0||0===br[t.getMode()][n]){let n=t.addBinaryShiftChar(e);r.push(n)}}static updateStateListForPair(t,e,r){const n=[];for(let i of t)this.updateStateForPair(i,e,r,n);return this.simplifyStates(n)}static updateStateForPair(t,e,r,n){let i=t.endBinaryShift(e);if(n.push(i.latchAndAppend(4,r)),4!==t.getMode()&&n.push(i.shiftAndAppend(4,r)),3===r||4===r){let t=i.latchAndAppend(2,16-r).latchAndAppend(2,1);n.push(t)}if(t.getBinaryShiftByteCount()>0){let r=t.addBinaryShiftChar(e).addBinaryShiftChar(e+1);n.push(r)}}static simplifyStates(t){let e=[];for(const r of t){let t=!0;for(const n of e){if(n.isBetterThanOrEqualTo(r)){t=!1;break}r.isBetterThanOrEqualTo(n)&&(e=e.filter((t=>t!==n)))}t&&e.push(r)}return e}}class Br{constructor(){}static encodeBytes(t){return Br.encode(t,Br.DEFAULT_EC_PERCENT,Br.DEFAULT_AZTEC_LAYERS)}static encode(t,e,r){let n,i,o,s,l,c=new Lr(t).encode(),h=w.truncDivision(c.getSize()*e,100)+11,u=c.getSize()+h;if(r!==Br.DEFAULT_AZTEC_LAYERS){if(n=r<0,i=Math.abs(r),i>(n?Br.MAX_NB_BITS_COMPACT:Br.MAX_NB_BITS))throw new a(S.format("Illegal value %s for layers",r));o=Br.totalBitsInLayer(i,n),s=Br.WORD_SIZE[i];let t=o-o%s;if(l=Br.stuffBits(c,s),l.getSize()+h>t)throw new a("Data to large for user specified layer");if(n&&l.getSize()>64*s)throw new a("Data to large for user specified layer")}else{s=0,l=null;for(let t=0;;t++){if(t>Br.MAX_NB_BITS)throw new a("Data too large for an Aztec code");if(n=t<=3,i=n?t+1:t,o=Br.totalBitsInLayer(i,n),u>o)continue;null!=l&&s===Br.WORD_SIZE[i]||(s=Br.WORD_SIZE[i],l=Br.stuffBits(c,s));let e=o-o%s;if(!(n&&l.getSize()>64*s)&&l.getSize()+h<=e)break}}let d,g=Br.generateCheckWords(l,o,s),f=l.getSize()/s,A=Br.generateModeMessage(n,i,f),m=(n?11:14)+4*i,E=new Int32Array(m);if(n){d=m;for(let t=0;t=n||t.get(o+r))&&(s|=1<{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var n={};(()=>{"use strict";var t;r.r(n),r.d(n,{Html5Qrcode:()=>L,Html5QrcodeScanner:()=>H,Html5QrcodeScannerState:()=>f,Html5QrcodeSupportedFormats:()=>t}),function(t){t[t.QR_CODE=0]="QR_CODE",t[t.AZTEC=1]="AZTEC",t[t.CODABAR=2]="CODABAR",t[t.CODE_39=3]="CODE_39",t[t.CODE_93=4]="CODE_93",t[t.CODE_128=5]="CODE_128",t[t.DATA_MATRIX=6]="DATA_MATRIX",t[t.MAXICODE=7]="MAXICODE",t[t.ITF=8]="ITF",t[t.EAN_13=9]="EAN_13",t[t.EAN_8=10]="EAN_8",t[t.PDF_417=11]="PDF_417",t[t.RSS_14=12]="RSS_14",t[t.RSS_EXPANDED=13]="RSS_EXPANDED",t[t.UPC_A=14]="UPC_A",t[t.UPC_E=15]="UPC_E",t[t.UPC_EAN_EXTENSION=16]="UPC_EAN_EXTENSION"}(t||(t={}));var e,i,o=new Map([[t.QR_CODE,"QR_CODE"],[t.AZTEC,"AZTEC"],[t.CODABAR,"CODABAR"],[t.CODE_39,"CODE_39"],[t.CODE_93,"CODE_93"],[t.CODE_128,"CODE_128"],[t.DATA_MATRIX,"DATA_MATRIX"],[t.MAXICODE,"MAXICODE"],[t.ITF,"ITF"],[t.EAN_13,"EAN_13"],[t.EAN_8,"EAN_8"],[t.PDF_417,"PDF_417"],[t.RSS_14,"RSS_14"],[t.RSS_EXPANDED,"RSS_EXPANDED"],[t.UPC_A,"UPC_A"],[t.UPC_E,"UPC_E"],[t.UPC_EAN_EXTENSION,"UPC_EAN_EXTENSION"]]);function s(e){return Object.values(t).includes(e)}!function(t){t[t.UNKNOWN=0]="UNKNOWN",t[t.URL=1]="URL"}(e||(e={})),function(t){t[t.SCAN_TYPE_CAMERA=0]="SCAN_TYPE_CAMERA",t[t.SCAN_TYPE_FILE=1]="SCAN_TYPE_FILE"}(i||(i={}));var a,l=function(){function t(){}return t.GITHUB_PROJECT_URL="https://github.com/mebjas/html5-qrcode",t.SCAN_DEFAULT_FPS=2,t.DEFAULT_DISABLE_FLIP=!1,t.DEFAULT_REMEMBER_LAST_CAMERA_USED=!0,t}(),c=function(){function t(t,e){this.format=t,this.formatName=e}return t.prototype.toString=function(){return this.formatName},t.create=function(e){if(!o.has(e))throw e+" not in html5QrcodeSupportedFormatsTextMap";return new t(e,o.get(e))},t}(),h=function(){function t(){}return t.createFromText=function(t){return{decodedText:t,result:{text:t}}},t.createFromQrcodeResult=function(t){return{decodedText:t.text,result:t}},t}();!function(t){t[t.UNKWOWN_ERROR=0]="UNKWOWN_ERROR",t[t.IMPLEMENTATION_ERROR=1]="IMPLEMENTATION_ERROR",t[t.NO_CODE_FOUND_ERROR=2]="NO_CODE_FOUND_ERROR"}(a||(a={}));var u=function(){function t(){}return t.createFrom=function(t){return{errorMessage:t,type:a.UNKWOWN_ERROR}},t}(),d=function(){function t(t){this.verbose=t}return t.prototype.log=function(t){this.verbose&&console.log(t)},t.prototype.warn=function(t){this.verbose&&console.warn(t)},t.prototype.logError=function(t,e){(this.verbose||!0===e)&&console.error(t)},t.prototype.logErrors=function(t){if(0===t.length)throw"Logger#logError called without arguments";this.verbose&&console.error(t)},t}();function g(t){return null==t}var f,w=function(){function t(){}return t.codeParseError=function(t){return"QR code parse error, error = "+t},t.errorGettingUserMedia=function(t){return"Error getting userMedia, error = "+t},t.onlyDeviceSupportedError=function(){return"The device doesn't support navigator.mediaDevices , only supported cameraIdOrConfig in this case is deviceId parameter (string)."},t.cameraStreamingNotSupported=function(){return"Camera streaming not supported by the browser."},t.unableToQuerySupportedDevices=function(){return"Unable to query supported devices, unknown error."},t.insecureContextCameraQueryError=function(){return"Camera access is only supported in secure context like https or localhost."},t}(),A=function(){function t(){}return t.scanningStatus=function(){return"Scanning"},t.idleStatus=function(){return"Idle"},t.errorStatus=function(){return"Error"},t.permissionStatus=function(){return"Permission"},t.noCameraFoundErrorStatus=function(){return"No Cameras"},t.lastMatch=function(t){return"Last Match: "+t},t.codeScannerTitle=function(){return"Code Scanner"},t.cameraPermissionTitle=function(){return"Request Camera Permissions"},t.cameraPermissionRequesting=function(){return"Requesting camera permissions..."},t.noCameraFound=function(){return"No camera found"},t.scanButtonStopScanningText=function(){return"Stop Scanning"},t.scanButtonStartScanningText=function(){return"Start Scanning"},t.scanButtonScanningStarting=function(){return"Launching Camera..."},t.textIfCameraScanSelected=function(){return"Scan an Image File"},t.textIfFileScanSelected=function(){return"Scan using camera directly"},t.selectCamera=function(){return"Select Camera"},t}(),m=function(){function t(){}return t.builtUsing=function(){return"Built using "},t.reportIssues=function(){return"Report issues"},t}(),E=function(){function t(){}return t.isMediaStreamConstraintsValid=function(t,e){if("object"!=typeof t){var r=typeof t;return e.logError("videoConstraints should be of type object, the object passed is of type "+r+".",!0),!1}for(var n=new Set(["autoGainControl","channelCount","echoCancellation","latency","noiseSuppression","sampleRate","sampleSize","volume"]),i=0,o=Object.keys(t);i0&&i[i.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]r&&(r=s,e=o)}if(!e)throw"No largest barcode found";return e},e.prototype.createBarcodeDetectorFormats=function(t){for(var e=[],r=0,n=t;r=o&&i()}))}))},e.prototype.scanFile=function(t,e){return this.scanFileV2(t,e).then((function(t){return t.decodedText}))},e.prototype.scanFileV2=function(t,e){var r=this;if(!(t&&t instanceof File))throw"imageFile argument is mandatory and should be instance of File. Use 'event.target.files[0]'.";if(g(e)&&(e=!0),!this.stateManagerProxy.canScanFile())throw"Cannot start file scan - ongoing camera scan";return new Promise((function(n,i){r.possiblyCloseLastScanImageFile(),r.clearElement(),r.lastScanImageFile=URL.createObjectURL(t);var o=new Image;o.onload=function(){var t=o.width,s=o.height,a=document.getElementById(r.elementId),l=a.clientWidth?a.clientWidth:O.DEFAULT_WIDTH,c=Math.max(a.clientHeight?a.clientHeight:s,O.FILE_SCAN_MIN_HEIGHT),u=r.computeCanvasDrawConfig(t,s,l,c);if(e){var d=r.createCanvasElement(l,c,"qr-canvas-visible");d.style.display="inline-block",a.appendChild(d);var g=d.getContext("2d");if(!g)throw"Unable to get 2d context from canvas";g.canvas.width=l,g.canvas.height=c,g.drawImage(o,0,0,t,s,u.x,u.y,u.width,u.height)}var f=r.createCanvasElement(u.width,u.height);a.appendChild(f);var w=f.getContext("2d");if(!w)throw"Unable to get 2d context from canvas";w.canvas.width=u.width,w.canvas.height=u.height,w.drawImage(o,0,0,t,s,0,0,u.width,u.height);try{r.qrcode.decodeAsync(f).then((function(t){n(h.createFromQrcodeResult(t))})).catch(i)}catch(t){i("QR code parse error, error = "+t)}},o.onerror=i,o.onabort=i,o.onstalled=i,o.onsuspend=i,o.src=URL.createObjectURL(t)}))},e.prototype.clear=function(){this.clearElement()},e.getCameras=function(){if(navigator.mediaDevices)return e.getCamerasFromMediaDevices();var t=MediaStreamTrack;if(MediaStreamTrack&&t.getSources)return e.getCamerasFromMediaStreamTrack();var r=w.unableToQuerySupportedDevices();return function(){if("https:"===location.protocol)return!0;var t=location.host.split(":")[0];return"127.0.0.1"===t||"localhost"===t}()||(r=w.insecureContextCameraQueryError()),Promise.reject(r)},e.prototype.getRunningTrackCapabilities=function(){if(null==this.localMediaStream)throw"Scanning is not in running state, call this API only when QR code scanning using camera is in running state.";if(0===this.localMediaStream.getVideoTracks().length)throw"No video tracks found";return this.localMediaStream.getVideoTracks()[0].getCapabilities()},e.prototype.applyVideoConstraints=function(t){var e=this;if(!t)throw"videoConstaints is required argument.";if(!E.isMediaStreamConstraintsValid(t,this.logger))throw"invalid videoConstaints passed, check logs for more details";if(null===this.localMediaStream)throw"Scanning is not in running state, call this API only when QR code scanning using camera is in running state.";if(0===this.localMediaStream.getVideoTracks().length)throw"No video tracks found";return new Promise((function(r,n){"aspectRatio"in t?n("Chaning 'aspectRatio' in run-time is not yet supported."):e.localMediaStream.getVideoTracks()[0].applyConstraints(t).then((function(t){r(t)})).catch((function(t){n(t)}))}))},e.getCamerasFromMediaDevices=function(){return new Promise((function(t,e){navigator.mediaDevices.getUserMedia({audio:!1,video:!0}).then((function(r){navigator.mediaDevices.enumerateDevices().then((function(e){for(var n=[],i=0,o=e;it&&(this.logger.warn("`qrbox.width` or `qrbox` is larger than the width of the root element. The width will be truncated to the width of root element."),i=t),i)},e.prototype.validateQrboxConfig=function(t){if("number"!=typeof t&&"function"!=typeof t&&(void 0===t.width||void 0===t.height))throw"Invalid instance of QrDimensions passed for 'config.qrbox'. Both 'width' and 'height' should be set."},e.prototype.toQrdimensions=function(t,e,r){if("number"==typeof r)return{width:r,height:r};if("function"==typeof r)try{return r(t,e)}catch(t){throw new Error("qrbox config was passed as a function but it failed with unknown error"+t)}return r},e.prototype.setupUi=function(t,e,r){r.isShadedBoxEnabled()&&this.validateQrboxSize(t,e,r);var n=g(r.qrbox)?{width:t,height:e}:r.qrbox;this.validateQrboxConfig(n);var i=this.toQrdimensions(t,e,n);i.height>e&&this.logger.warn("[Html5Qrcode] config.qrbox has height that isgreater than the height of the video stream. Shading will be ignored");var o=r.isShadedBoxEnabled()&&i.height<=e,s={x:0,y:0,width:t,height:e},a=o?this.getShadedRegionBounds(t,e,i):s,l=this.createCanvasElement(a.width,a.height),c=l.getContext("2d");c.canvas.width=a.width,c.canvas.height=a.height,this.element.append(l),o&&this.possiblyInsertShadingElement(this.element,t,e,i),this.createScannerPausedUiElement(this.element),this.qrRegion=a,this.context=c,this.canvasElement=l},e.prototype.createScannerPausedUiElement=function(t){var e=document.createElement("div");e.innerText="Scanner paused",e.style.display="none",e.style.position="absolute",e.style.top="0px",e.style.zIndex="1",e.style.background="yellow",e.style.textAlign="center",e.style.width="100%",t.appendChild(e),this.scannerPausedUiElement=e},e.prototype.scanContext=function(t,e){var r=this;return this.stateManagerProxy.isPaused()?Promise.resolve(!1):this.qrcode.decodeAsync(this.canvasElement).then((function(e){return t(e.text,h.createFromQrcodeResult(e)),r.possiblyUpdateShaders(!0),!0})).catch((function(t){r.possiblyUpdateShaders(!1);var n=w.codeParseError(t);return e(n,u.createFrom(n)),!1}))},e.prototype.foreverScan=function(t,e,r){var n=this;if(this.shouldScan&&this.localMediaStream){var i=this.videoElement,o=i.videoWidth/i.clientWidth,s=i.videoHeight/i.clientHeight;if(!this.qrRegion)throw"qrRegion undefined when localMediaStream is ready.";var a=this.qrRegion.width*o,l=this.qrRegion.height*s,c=this.qrRegion.x*o,h=this.qrRegion.y*s;this.context.drawImage(i,c,h,a,l,0,0,this.qrRegion.width,this.qrRegion.height);var u=function(){n.foreverScanTimeout=setTimeout((function(){n.foreverScan(t,e,r)}),n.getTimeoutFps(t.fps))};this.scanContext(e,r).then((function(i){i||!0===t.disableFlip?u():(n.context.translate(n.context.canvas.width,0),n.context.scale(-1,1),n.scanContext(e,r).finally((function(){u()})))})).catch((function(t){n.logger.logError("Error happend while scanning context",t),u()}))}},e.prototype.onMediaStreamReceived=function(t,e,r,n,i,o){var s=this,a=this;return new Promise((function(l,c){var h=function(){var r=s.createVideoElement(n);a.element.append(r),r.onabort=c,r.onerror=c;var h=function(){var t=r.clientWidth,n=r.clientHeight;a.setupUi(t,n,e),a.foreverScan(e,i,o),r.removeEventListener("playing",h),l(null)};r.addEventListener("playing",h),r.srcObject=t,r.play(),a.videoElement=r};if(a.localMediaStream=t,r||!e.aspectRatio)h();else{var u={aspectRatio:e.aspectRatio};t.getVideoTracks()[0].applyConstraints(u).then((function(t){return h()})).catch((function(t){a.logger.logErrors(["[Html5Qrcode] Constriants could not be satisfied, ignoring constraints",t]),h()}))}}))},e.prototype.createVideoConstraints=function(t){if("string"==typeof t)return{deviceId:{exact:t}};if("object"==typeof t){var e="facingMode",r={user:!0,environment:!0},n="exact",i=function(t){if(t in r)return!0;throw"config has invalid 'facingMode' value = '"+t+"'"},o=Object.keys(t);if(1!==o.length)throw"'cameraIdOrConfig' object should have exactly 1 key, if passed as an object, found "+o.length+" keys";var s=Object.keys(t)[0];if(s!==e&&"deviceId"!==s)throw"Only 'facingMode' and 'deviceId' are supported for 'cameraIdOrConfig'";if(s!==e){var a=t.deviceId;if("string"==typeof a)return{deviceId:a};if("object"==typeof a){if(n in a)return{deviceId:{exact:a.exact}};throw"'deviceId' should be string or object with exact as key."}throw"Invalid type of 'deviceId' = "+typeof a}var l=t.facingMode;if("string"==typeof l){if(i(l))return{facingMode:l}}else{if("object"!=typeof l)throw"Invalid type of 'facingMode' = "+typeof l;if(!(n in l))throw"'facingMode' should be string or object with exact as key.";if(i(l.exact))return{facingMode:{exact:l.exact}}}}throw"Invalid type of 'cameraIdOrConfig' = "+typeof t},e.prototype.computeCanvasDrawConfig=function(t,e,r,n){if(t<=r&&e<=n)return{x:(r-t)/2,y:(n-e)/2,width:t,height:e};var i=t,o=e;return t>r&&(e*=r/t,t=r),e>n&&(t*=n/e,e=n),this.logger.log("Image downsampled from "+i+"X"+o+" to "+t+"X"+e+"."),this.computeCanvasDrawConfig(t,e,r,n)},e.prototype.clearElement=function(){if(this.stateManagerProxy.isScanning())throw"Cannot clear while scan is ongoing, close it first.";var t=document.getElementById(this.elementId);t&&(t.innerHTML="")},e.prototype.createVideoElement=function(t){var e=document.createElement("video");return e.style.width=t+"px",e.muted=!0,e.setAttribute("muted","true"),e.playsInline=!0,e},e.prototype.possiblyUpdateShaders=function(t){this.qrMatch!==t&&(this.hasBorderShaders&&this.borderShaders&&this.borderShaders.length&&this.borderShaders.forEach((function(e){e.style.backgroundColor=t?O.BORDER_SHADER_MATCH_COLOR:O.BORDER_SHADER_DEFAULT_COLOR})),this.qrMatch=t)},e.prototype.possiblyCloseLastScanImageFile=function(){this.lastScanImageFile&&(URL.revokeObjectURL(this.lastScanImageFile),this.lastScanImageFile=null)},e.prototype.createCanvasElement=function(t,e,r){var n=t,i=e,o=document.createElement("canvas");return o.style.width=n+"px",o.style.height=i+"px",o.style.display="none",o.id=g(r)?"qr-canvas":r,o},e.prototype.getShadedRegionBounds=function(t,e,r){if(r.width>t||r.height>e)throw"'config.qrbox' dimensions should not be greater than the dimensions of the root HTML element.";return{x:(t-r.width)/2,y:(e-r.height)/2,width:r.width,height:r.height}},e.prototype.possiblyInsertShadingElement=function(t,e,r,n){if(!(e-n.width<1||r-n.height<1)){var i=document.createElement("div");i.style.position="absolute";var o=(e-n.width)/2,s=(r-n.height)/2;if(i.style.borderLeft=o+"px solid #0000007a",i.style.borderRight=o+"px solid #0000007a",i.style.borderTop=s+"px solid #0000007a",i.style.borderBottom=s+"px solid #0000007a",i.style.boxSizing="border-box",i.style.top="0px",i.style.bottom="0px",i.style.left="0px",i.style.right="0px",i.id=""+O.SHADED_REGION_ELEMENT_ID,e-n.width<11||r-n.height<11)this.hasBorderShaders=!1;else{var a=40;this.insertShaderBorders(i,a,5,-5,0,!0),this.insertShaderBorders(i,a,5,-5,0,!1),this.insertShaderBorders(i,a,5,n.height+5,0,!0),this.insertShaderBorders(i,a,5,n.height+5,0,!1),this.insertShaderBorders(i,5,45,-5,-5,!0),this.insertShaderBorders(i,5,45,n.height+5-a,-5,!0),this.insertShaderBorders(i,5,45,-5,-5,!1),this.insertShaderBorders(i,5,45,n.height+5-a,-5,!1),this.hasBorderShaders=!0}t.append(i)}},e.prototype.insertShaderBorders=function(t,e,r,n,i,o){var s=document.createElement("div");s.style.position="absolute",s.style.backgroundColor=O.BORDER_SHADER_DEFAULT_COLOR,s.style.width=e+"px",s.style.height=r+"px",s.style.top=n+"px",o?s.style.left=i+"px":s.style.right=i+"px",this.borderShaders||(this.borderShaders=[]),this.borderShaders.push(s),t.appendChild(s)},e.prototype.showPausedState=function(){if(!this.scannerPausedUiElement)throw"[internal error] scanner paused UI element not found";this.scannerPausedUiElement.style.display="block"},e.prototype.hidePausedState=function(){if(!this.scannerPausedUiElement)throw"[internal error] scanner paused UI element not found";this.scannerPausedUiElement.style.display="none"},e.prototype.getTimeoutFps=function(t){return 1e3/t},e}(),B="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NjAgNDYwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0NjAgNDYwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBkPSJNMjMwIDBDMTAyLjk3NSAwIDAgMTAyLjk3NSAwIDIzMHMxMDIuOTc1IDIzMCAyMzAgMjMwIDIzMC0xMDIuOTc0IDIzMC0yMzBTMzU3LjAyNSAwIDIzMCAwem0zOC4zMzMgMzc3LjM2YzAgOC42NzYtNy4wMzQgMTUuNzEtMTUuNzEgMTUuNzFoLTQzLjEwMWMtOC42NzYgMC0xNS43MS03LjAzNC0xNS43MS0xNS43MVYyMDIuNDc3YzAtOC42NzYgNy4wMzMtMTUuNzEgMTUuNzEtMTUuNzFoNDMuMTAxYzguNjc2IDAgMTUuNzEgNy4wMzMgMTUuNzEgMTUuNzFWMzc3LjM2ek0yMzAgMTU3Yy0yMS41MzkgMC0zOS0xNy40NjEtMzktMzlzMTcuNDYxLTM5IDM5LTM5IDM5IDE3LjQ2MSAzOSAzOS0xNy40NjEgMzktMzkgMzl6Ii8+PC9zdmc+",P=function(){function t(){}return t.createDefault=function(){return{hasPermission:!1,lastUsedCameraId:null}},t}(),v=function(){function t(){this.data=P.createDefault();var e=localStorage.getItem(t.LOCAL_STORAGE_KEY);e?this.data=JSON.parse(e):this.reset()}return t.prototype.hasCameraPermissions=function(){return this.data.hasPermission},t.prototype.getLastUsedCameraId=function(){return this.data.lastUsedCameraId},t.prototype.setHasPermission=function(t){this.data.hasPermission=t,this.flush()},t.prototype.setLastUsedCameraId=function(t){this.data.lastUsedCameraId=t,this.flush()},t.prototype.resetLastUsedCameraId=function(){this.data.lastUsedCameraId=null,this.flush()},t.prototype.reset=function(){this.data=P.createDefault(),this.flush()},t.prototype.flush=function(){localStorage.setItem(t.LOCAL_STORAGE_KEY,JSON.stringify(this.data))},t.LOCAL_STORAGE_KEY="HTML5_QRCODE_DATA",t}(),F=function(){function t(){this.infoDiv=document.createElement("div")}return t.prototype.renderInto=function(t){this.infoDiv.style.position="absolute",this.infoDiv.style.top="10px",this.infoDiv.style.right="10px",this.infoDiv.style.zIndex="2",this.infoDiv.style.display="none",this.infoDiv.style.padding="5pt",this.infoDiv.style.border="1px solid silver",this.infoDiv.style.fontSize="10pt",this.infoDiv.style.background="rgb(248 248 248)",this.infoDiv.innerText=m.builtUsing();var e=document.createElement("a");e.innerText="html5-qrcode",e.href="https://github.com/mebjas/html5-qrcode",e.target="new",this.infoDiv.appendChild(e);var r=document.createElement("br"),n=document.createElement("br");this.infoDiv.appendChild(r),this.infoDiv.appendChild(n);var i=document.createElement("a");i.innerText=m.reportIssues(),i.href="https://github.com/mebjas/html5-qrcode/issues",i.target="new",this.infoDiv.appendChild(i),t.appendChild(this.infoDiv)},t.prototype.show=function(){this.infoDiv.style.display="block"},t.prototype.hide=function(){this.infoDiv.style.display="none"},t}(),x=function(){function t(t,e){this.isShowingInfoIcon=!0,this.onTapIn=t,this.onTapOut=e,this.infoIcon=document.createElement("img")}return t.prototype.renderInto=function(t){var e=this;this.infoIcon.alt="Info icon",this.infoIcon.src=B,this.infoIcon.style.position="absolute",this.infoIcon.style.top="4px",this.infoIcon.style.right="4px",this.infoIcon.style.opacity="0.6",this.infoIcon.style.cursor="pointer",this.infoIcon.style.zIndex="2",this.infoIcon.style.width="16px",this.infoIcon.style.height="16px",this.infoIcon.onmouseover=function(t){return e.onHoverIn()},this.infoIcon.onmouseout=function(t){return e.onHoverOut()},this.infoIcon.onclick=function(t){return e.onClick()},t.appendChild(this.infoIcon)},t.prototype.onHoverIn=function(){this.isShowingInfoIcon&&(this.infoIcon.style.opacity="1")},t.prototype.onHoverOut=function(){this.isShowingInfoIcon&&(this.infoIcon.style.opacity="0.6")},t.prototype.onClick=function(){this.isShowingInfoIcon?(this.isShowingInfoIcon=!1,this.onTapIn(),this.infoIcon.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAQgAAAEIBarqQRAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAE1SURBVDiNfdI7S0NBEAXgLya1otFgpbYSbISAgpXYi6CmiH9KCAiChaVga6OiWPgfRDQ+0itaGVNosXtluWwcuMzePfM4M3sq8lbHBubwg1dc4m1E/J/N4ghDPOIsfk/4xiEao5KX0McFljN4C9d4QTPXuY99jP3DsIoDPGM6BY5i5yI5R7O4q+ImFkJY2DCh3cAH2klyB+9J1xUMMAG7eCh1a+Mr+k48b5diXrFVwwLuS+BJ9MfR7+G0FHOHhTHhnXNWS87VDF4pcnfQK4Ep7XScNLmPTZgURNKKYENYWDpzW1BhscS1WHS8CDgURFJQrWcoF3c13KKbgg1BYQfy8xZWEzTTw1QZbAoKu8FqJnktdu5hcVSHmchiILzzuaDQvjBzV2m8yohCE1jHfPx/xhU+y4G/D75ELlRJsSYAAAAASUVORK5CYII=",this.infoIcon.style.opacity="1"):(this.isShowingInfoIcon=!0,this.onTapOut(),this.infoIcon.src=B,this.infoIcon.style.opacity="0.6")},t}(),k=function(){function t(){var t=this;this.infoDiv=new F,this.infoIcon=new x((function(){t.infoDiv.show()}),(function(){t.infoDiv.hide()}))}return t.prototype.renderInto=function(t){this.infoDiv.renderInto(t),this.infoIcon.renderInto(t)},t}(),U=function(){function t(){}return t.hasCameraPermissions=function(){return new Promise((function(t,e){navigator.mediaDevices.enumerateDevices().then((function(e){e.forEach((function(e){"videoinput"===e.kind&&e.label&&t(!0)})),t(!1)}))}))},t}();!function(t){t[t.STATUS_DEFAULT=0]="STATUS_DEFAULT",t[t.STATUS_SUCCESS=1]="STATUS_SUCCESS",t[t.STATUS_WARNING=2]="STATUS_WARNING",t[t.STATUS_REQUESTING_PERMISSION=3]="STATUS_REQUESTING_PERMISSION"}(N||(N={}));var H=function(){function t(t,e,r){if(this.lastMatchFound=null,this.cameraScanImage=null,this.fileScanImage=null,this.elementId=t,this.config=this.createConfig(e),this.verbose=!0===r,!document.getElementById(t))throw"HTML Element with id="+t+" not found";this.currentScanType=i.SCAN_TYPE_CAMERA,this.sectionSwapAllowed=!0,this.logger=new d(this.verbose),this.persistedDataManager=new v,!0!==e.rememberLastUsedCamera&&this.persistedDataManager.reset()}return t.prototype.render=function(t,e){var r=this;this.lastMatchFound=null,this.qrCodeSuccessCallback=function(e,n){if(t)t(e,n);else{if(r.lastMatchFound===e)return;r.lastMatchFound=e,r.setHeaderMessage(A.lastMatch(e),N.STATUS_SUCCESS)}},this.qrCodeErrorCallback=function(t,r){e&&e(t,r)};var n,i,o=document.getElementById(this.elementId);if(!o)throw"HTML Element with id="+this.elementId+" not found";o.innerHTML="",this.createBasicLayout(o),this.html5Qrcode=new L(this.getScanRegionId(),(n=this.config,i=this.verbose,{formatsToSupport:n.formatsToSupport,experimentalFeatures:n.experimentalFeatures,verbose:i}))},t.prototype.pause=function(t){if(!this.html5Qrcode)throw"Code scanner not initialized.";(g(t)||!0!==t)&&(t=!1),this.html5Qrcode.pause(t)},t.prototype.resume=function(){if(!this.html5Qrcode)throw"Code scanner not initialized.";this.html5Qrcode.resume()},t.prototype.getState=function(){if(!this.html5Qrcode)throw"Code scanner not initialized.";return this.html5Qrcode.getState()},t.prototype.clear=function(){var t=this,e=function(){var e=document.getElementById(t.elementId);e&&(e.innerHTML="",t.resetBasicLayout(e))};return this.html5Qrcode?new Promise((function(r,n){t.html5Qrcode?t.html5Qrcode.isScanning?t.html5Qrcode.stop().then((function(n){t.html5Qrcode?(t.html5Qrcode.clear(),e(),r()):r()})).catch((function(e){t.verbose&&t.logger.logError("Unable to stop qrcode scanner",e),n(e)})):(t.html5Qrcode.clear(),e()):r()})):Promise.resolve()},t.prototype.getRunningTrackCapabilities=function(){if(!this.html5Qrcode)throw"Code scanner not initialized.";return this.html5Qrcode.getRunningTrackCapabilities()},t.prototype.applyVideoConstraints=function(t){if(!this.html5Qrcode)throw"Code scanner not initialized.";return this.html5Qrcode.applyVideoConstraints(t)},t.prototype.createConfig=function(t){return t?(t.fps||(t.fps=l.SCAN_DEFAULT_FPS),t.rememberLastUsedCamera!==!l.DEFAULT_REMEMBER_LAST_CAMERA_USED&&(t.rememberLastUsedCamera=l.DEFAULT_REMEMBER_LAST_CAMERA_USED),t):{fps:l.SCAN_DEFAULT_FPS,rememberLastUsedCamera:l.DEFAULT_REMEMBER_LAST_CAMERA_USED}},t.prototype.createBasicLayout=function(t){t.style.position="relative",t.style.padding="0px",t.style.border="1px solid silver",this.createHeader(t);var e=document.createElement("div"),r=this.getScanRegionId();e.id=r,e.style.width="100%",e.style.minHeight="100px",e.style.textAlign="center",t.appendChild(e),this.insertCameraScanImageToScanRegion();var n=document.createElement("div"),i=this.getDashboardId();n.id=i,n.style.width="100%",t.appendChild(n),this.setupInitialDashboard(n)},t.prototype.resetBasicLayout=function(t){t.style.border="none"},t.prototype.setupInitialDashboard=function(t){this.createSection(t),this.createSectionControlPanel(),this.createSectionSwap()},t.prototype.createHeader=function(t){var e=document.createElement("div");e.style.textAlign="left",e.style.margin="0px",t.appendChild(e),(new k).renderInto(e);var r=document.createElement("div");r.id=this.getHeaderMessageContainerId(),r.style.display="none",r.style.textAlign="center",r.style.fontSize="14px",r.style.padding="2px 10px",r.style.margin="4px",r.style.borderTop="1px solid #f6f6f6",e.appendChild(r)},t.prototype.createSection=function(t){var e=document.createElement("div");e.id=this.getDashboardSectionId(),e.style.width="100%",e.style.padding="10px 0px 10px 0px",e.style.textAlign="left",t.appendChild(e)},t.prototype.createCameraListUi=function(t,e,r){var n=this;n.setHeaderMessage(A.cameraPermissionRequesting());var i=function(){r||n.createPermissionButton(t,e)};L.getCameras().then((function(r){n.persistedDataManager.setHasPermission(!0),n.resetHeaderMessage(),r&&r.length>0?(t.removeChild(e),n.renderCameraSelection(r)):(n.setHeaderMessage(A.noCameraFound(),N.STATUS_WARNING),i())})).catch((function(t){n.persistedDataManager.setHasPermission(!1),r?r.disabled=!1:i(),n.setHeaderMessage(t,N.STATUS_WARNING)}))},t.prototype.createPermissionButton=function(t,e){var r=this,n=document.createElement("button");n.innerText=A.cameraPermissionTitle(),n.addEventListener("click",(function(){n.disabled=!0,r.createCameraListUi(t,e,n)})),e.appendChild(n)},t.prototype.createPermissionsUi=function(t,e){var r=this;this.persistedDataManager.hasCameraPermissions()?U.hasCameraPermissions().then((function(n){n?r.createCameraListUi(t,e):(r.persistedDataManager.setHasPermission(!1),r.createPermissionButton(t,e))})).catch((function(n){r.persistedDataManager.setHasPermission(!1),r.createPermissionButton(t,e)})):this.createPermissionButton(t,e)},t.prototype.createSectionControlPanel=function(){var t=this,e=document.getElementById(this.getDashboardSectionId()),r=document.createElement("div");e.appendChild(r);var n=document.createElement("div");n.id=this.getDashboardSectionCameraScanRegionId(),n.style.display=this.currentScanType===i.SCAN_TYPE_CAMERA?"block":"none",r.appendChild(n);var o=document.createElement("div");o.style.textAlign="center",n.appendChild(o),this.createPermissionsUi(n,o);var s=document.createElement("div");s.id=this.getDashboardSectionFileScanRegionId(),s.style.textAlign="center",s.style.display=this.currentScanType===i.SCAN_TYPE_CAMERA?"none":"block",r.appendChild(s);var a=document.createElement("input");a.id=this.getFileScanInputId(),a.accept="image/*",a.type="file",a.style.width="200px",a.disabled=this.currentScanType===i.SCAN_TYPE_CAMERA;var l=document.createElement("span");l.innerText=" Select Image",s.appendChild(a),s.appendChild(l),a.addEventListener("change",(function(e){if(!t.html5Qrcode)throw"html5Qrcode not defined";if(null!=e&&null!=e.target&&t.currentScanType===i.SCAN_TYPE_FILE&&0!==e.target.files.length){var r=e.target.files[0];t.html5Qrcode.scanFileV2(r,!0).then((function(e){t.resetHeaderMessage(),t.qrCodeSuccessCallback(e.decodedText,e)})).catch((function(e){t.setHeaderMessage(e,N.STATUS_WARNING),t.qrCodeErrorCallback(e,u.createFrom(e))}))}}))},t.prototype.renderCameraSelection=function(t){var e=this,r=document.getElementById(this.getDashboardSectionCameraScanRegionId());r.style.textAlign="center";var n=document.createElement("span");n.style.marginRight="10px";var i=t.length,o=document.createElement("select");if(1===i)o.style.display="none";else{var s=A.selectCamera();n.innerText=s+" ("+t.length+") "}o.id=this.getCameraSelectionId();for(var a=[],l=0,c=t;l",e.appendChild(t.cameraScanImage)},this.cameraScanImage.width=64,this.cameraScanImage.style.opacity="0.8",this.cameraScanImage.src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNzEuNjQzIDM3MS42NDMiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDM3MS42NDMgMzcxLjY0MyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTEwNS4wODQgMzguMjcxaDE2My43Njh2MjBIMTA1LjA4NHoiLz48cGF0aCBkPSJNMzExLjU5NiAxOTAuMTg5Yy03LjQ0MS05LjM0Ny0xOC40MDMtMTYuMjA2LTMyLjc0My0yMC41MjJWMzBjMC0xNi41NDItMTMuNDU4LTMwLTMwLTMwSDEyNS4wODRjLTE2LjU0MiAwLTMwIDEzLjQ1OC0zMCAzMHYxMjAuMTQzaC04LjI5NmMtMTYuNTQyIDAtMzAgMTMuNDU4LTMwIDMwdjEuMzMzYTI5LjgwNCAyOS44MDQgMCAwIDAgNC42MDMgMTUuOTM5Yy03LjM0IDUuNDc0LTEyLjEwMyAxNC4yMjEtMTIuMTAzIDI0LjA2MXYxLjMzM2MwIDkuODQgNC43NjMgMTguNTg3IDEyLjEwMyAyNC4wNjJhMjkuODEgMjkuODEgMCAwIDAtNC42MDMgMTUuOTM4djEuMzMzYzAgMTYuNTQyIDEzLjQ1OCAzMCAzMCAzMGg4LjMyNGMuNDI3IDExLjYzMSA3LjUwMyAyMS41ODcgMTcuNTM0IDI2LjE3Ny45MzEgMTAuNTAzIDQuMDg0IDMwLjE4NyAxNC43NjggNDUuNTM3YTkuOTg4IDkuOTg4IDAgMCAwIDguMjE2IDQuMjg4IDkuOTU4IDkuOTU4IDAgMCAwIDUuNzA0LTEuNzkzYzQuNTMzLTMuMTU1IDUuNjUtOS4zODggMi40OTUtMTMuOTIxLTYuNzk4LTkuNzY3LTkuNjAyLTIyLjYwOC0xMC43Ni0zMS40aDgyLjY4NWMuMjcyLjQxNC41NDUuODE4LjgxNSAxLjIxIDMuMTQyIDQuNTQxIDkuMzcyIDUuNjc5IDEzLjkxMyAyLjUzNCA0LjU0Mi0zLjE0MiA1LjY3Ny05LjM3MSAyLjUzNS0xMy45MTMtMTEuOTE5LTE3LjIyOS04Ljc4Ny0zNS44ODQgOS41ODEtNTcuMDEyIDMuMDY3LTIuNjUyIDEyLjMwNy0xMS43MzIgMTEuMjE3LTI0LjAzMy0uODI4LTkuMzQzLTcuMTA5LTE3LjE5NC0xOC42NjktMjMuMzM3YTkuODU3IDkuODU3IDAgMCAwLTEuMDYxLS40ODZjLS40NjYtLjE4Mi0xMS40MDMtNC41NzktOS43NDEtMTUuNzA2IDEuMDA3LTYuNzM3IDE0Ljc2OC04LjI3MyAyMy43NjYtNy42NjYgMjMuMTU2IDEuNTY5IDM5LjY5OCA3LjgwMyA0Ny44MzYgMTguMDI2IDUuNzUyIDcuMjI1IDcuNjA3IDE2LjYyMyA1LjY3MyAyOC43MzMtLjQxMyAyLjU4NS0uODI0IDUuMjQxLTEuMjQ1IDcuOTU5LTUuNzU2IDM3LjE5NC0xMi45MTkgODMuNDgzLTQ5Ljg3IDExNC42NjEtNC4yMjEgMy41NjEtNC43NTYgOS44Ny0xLjE5NCAxNC4wOTJhOS45OCA5Ljk4IDAgMCAwIDcuNjQ4IDMuNTUxIDkuOTU1IDkuOTU1IDAgMCAwIDYuNDQ0LTIuMzU4YzQyLjY3Mi0zNi4wMDUgNTAuODAyLTg4LjUzMyA1Ni43MzctMTI2Ljg4OC40MTUtMi42ODQuODIxLTUuMzA5IDEuMjI5LTcuODYzIDIuODM0LTE3LjcyMS0uNDU1LTMyLjY0MS05Ljc3Mi00NC4zNDV6bS0yMzIuMzA4IDQyLjYyYy01LjUxNCAwLTEwLTQuNDg2LTEwLTEwdi0xLjMzM2MwLTUuNTE0IDQuNDg2LTEwIDEwLTEwaDE1djIxLjMzM2gtMTV6bS0yLjUtNTIuNjY2YzAtNS41MTQgNC40ODYtMTAgMTAtMTBoNy41djIxLjMzM2gtNy41Yy01LjUxNCAwLTEwLTQuNDg2LTEwLTEwdi0xLjMzM3ptMTcuNSA5My45OTloLTcuNWMtNS41MTQgMC0xMC00LjQ4Ni0xMC0xMHYtMS4zMzNjMC01LjUxNCA0LjQ4Ni0xMCAxMC0xMGg3LjV2MjEuMzMzem0zMC43OTYgMjguODg3Yy01LjUxNCAwLTEwLTQuNDg2LTEwLTEwdi04LjI3MWg5MS40NTdjLS44NTEgNi42NjgtLjQzNyAxMi43ODcuNzMxIDE4LjI3MWgtODIuMTg4em03OS40ODItMTEzLjY5OGMtMy4xMjQgMjAuOTA2IDEyLjQyNyAzMy4xODQgMjEuNjI1IDM3LjA0IDUuNDQxIDIuOTY4IDcuNTUxIDUuNjQ3IDcuNzAxIDcuMTg4LjIxIDIuMTUtMi41NTMgNS42ODQtNC40NzcgNy4yNTEtLjQ4Mi4zNzgtLjkyOS44LTEuMzM1IDEuMjYxLTYuOTg3IDcuOTM2LTExLjk4MiAxNS41Mi0xNS40MzIgMjIuNjg4aC05Ny41NjRWMzBjMC01LjUxNCA0LjQ4Ni0xMCAxMC0xMGgxMjMuNzY5YzUuNTE0IDAgMTAgNC40ODYgMTAgMTB2MTM1LjU3OWMtMy4wMzItLjM4MS02LjE1LS42OTQtOS4zODktLjkxNC0yNS4xNTktMS42OTQtNDIuMzcgNy43NDgtNDQuODk4IDI0LjY2NnoiLz48cGF0aCBkPSJNMTc5LjEyOSA4My4xNjdoLTI0LjA2YTUgNSAwIDAgMC01IDV2MjQuMDYxYTUgNSAwIDAgMCA1IDVoMjQuMDZhNSA1IDAgMCAwIDUtNVY4OC4xNjdhNSA1IDAgMCAwLTUtNXpNMTcyLjYyOSAxNDIuODZoLTEyLjU2VjEzMC44YTUgNSAwIDEgMC0xMCAwdjE3LjA2MWE1IDUgMCAwIDAgNSA1aDE3LjU2YTUgNSAwIDEgMCAwLTEwLjAwMXpNMjE2LjU2OCA4My4xNjdoLTI0LjA2YTUgNSAwIDAgMC01IDV2MjQuMDYxYTUgNSAwIDAgMCA1IDVoMjQuMDZhNSA1IDAgMCAwIDUtNVY4OC4xNjdhNSA1IDAgMCAwLTUtNXptLTUgMjQuMDYxaC0xNC4wNlY5My4xNjdoMTQuMDZ2MTQuMDYxek0yMTEuNjY5IDEyNS45MzZIMTk3LjQxYTUgNSAwIDAgMC01IDV2MTQuMjU3YTUgNSAwIDAgMCA1IDVoMTQuMjU5YTUgNSAwIDAgMCA1LTV2LTE0LjI1N2E1IDUgMCAwIDAtNS01eiIvPjwvc3ZnPg=="},t.prototype.insertFileScanImageToScanRegion=function(){var t=this,e=document.getElementById(this.getScanRegionId());if(this.fileScanImage)return e.innerHTML="
",void e.appendChild(this.fileScanImage);this.fileScanImage=new Image,this.fileScanImage.onload=function(r){e.innerHTML="
",e.appendChild(t.fileScanImage)},this.fileScanImage.width=64,this.fileScanImage.style.opacity="0.8",this.fileScanImage.src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1OS4wMTggNTkuMDE4IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1OS4wMTggNTkuMDE4IiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBkPSJtNTguNzQxIDU0LjgwOS01Ljk2OS02LjI0NGExMC43NCAxMC43NCAwIDAgMCAyLjgyLTcuMjVjMC01Ljk1My00Ljg0My0xMC43OTYtMTAuNzk2LTEwLjc5NlMzNCAzNS4zNjEgMzQgNDEuMzE0IDM4Ljg0MyA1Mi4xMSA0NC43OTYgNTIuMTFjMi40NDEgMCA0LjY4OC0uODI0IDYuNDk5LTIuMTk2bDYuMDAxIDYuMjc3YS45OTguOTk4IDAgMCAwIDEuNDE0LjAzMiAxIDEgMCAwIDAgLjAzMS0xLjQxNHpNMzYgNDEuMzE0YzAtNC44NSAzLjk0Ni04Ljc5NiA4Ljc5Ni04Ljc5NnM4Ljc5NiAzLjk0NiA4Ljc5NiA4Ljc5Ni0zLjk0NiA4Ljc5Ni04Ljc5NiA4Ljc5NlMzNiA0Ni4xNjQgMzYgNDEuMzE0ek0xMC40MzEgMTYuMDg4YzAgMy4wNyAyLjQ5OCA1LjU2OCA1LjU2OSA1LjU2OHM1LjU2OS0yLjQ5OCA1LjU2OS01LjU2OGMwLTMuMDcxLTIuNDk4LTUuNTY5LTUuNTY5LTUuNTY5cy01LjU2OSAyLjQ5OC01LjU2OSA1LjU2OXptOS4xMzggMGMwIDEuOTY4LTEuNjAyIDMuNTY4LTMuNTY5IDMuNTY4cy0zLjU2OS0xLjYwMS0zLjU2OS0zLjU2OCAxLjYwMi0zLjU2OSAzLjU2OS0zLjU2OSAzLjU2OSAxLjYwMSAzLjU2OSAzLjU2OXoiLz48cGF0aCBkPSJtMzAuODgyIDI4Ljk4NyA5LjE4LTEwLjA1NCAxMS4yNjIgMTAuMzIzYTEgMSAwIDAgMCAxLjM1MS0xLjQ3NWwtMTItMTFhMSAxIDAgMCAwLTEuNDE0LjA2M2wtOS43OTQgMTAuNzI3LTQuNzQzLTQuNzQzYTEuMDAzIDEuMDAzIDAgMCAwLTEuMzY4LS4wNDRMNi4zMzkgMzcuNzY4YTEgMSAwIDEgMCAxLjMyMiAxLjUwMWwxNi4zMTMtMTQuMzYyIDcuMzE5IDcuMzE4YS45OTkuOTk5IDAgMSAwIDEuNDE0LTEuNDE0bC0xLjgyNS0xLjgyNHoiLz48cGF0aCBkPSJNMzAgNDYuNTE4SDJ2LTQyaDU0djI4YTEgMSAwIDEgMCAyIDB2LTI5YTEgMSAwIDAgMC0xLTFIMWExIDEgMCAwIDAtMSAxdjQ0YTEgMSAwIDAgMCAxIDFoMjlhMSAxIDAgMSAwIDAtMnoiLz48L3N2Zz4="},t.prototype.clearScanRegion=function(){document.getElementById(this.getScanRegionId()).innerHTML=""},t.prototype.getDashboardSectionId=function(){return this.elementId+"__dashboard_section"},t.prototype.getDashboardSectionCameraScanRegionId=function(){return this.elementId+"__dashboard_section_csr"},t.prototype.getDashboardSectionFileScanRegionId=function(){return this.elementId+"__dashboard_section_fsr"},t.prototype.getDashboardSectionSwapLinkId=function(){return this.elementId+"__dashboard_section_swaplink"},t.prototype.getScanRegionId=function(){return this.elementId+"__scan_region"},t.prototype.getDashboardId=function(){return this.elementId+"__dashboard"},t.prototype.getFileScanInputId=function(){return this.elementId+"__filescan_input"},t.prototype.getStatusSpanId=function(){return this.elementId+"__status_span"},t.prototype.getHeaderMessageContainerId=function(){return this.elementId+"__header_message"},t.prototype.getCameraSelectionId=function(){return this.elementId+"__camera_selection"},t.prototype.getCameraScanRegion=function(){return document.getElementById(this.getDashboardSectionCameraScanRegionId())},t.prototype.getFileScanRegion=function(){return document.getElementById(this.getDashboardSectionFileScanRegionId())},t.prototype.getFileScanInput=function(){return document.getElementById(this.getFileScanInputId())},t.prototype.getDashboardSectionSwapLink=function(){return document.getElementById(this.getDashboardSectionSwapLinkId())},t.prototype.getHeaderMessageDiv=function(){return document.getElementById(this.getHeaderMessageContainerId())},t}()})(),__Html5QrcodeLibrary__=n})();/** Append the libary components to globals for backwards compatibility. */ -if (window && !Html5QrcodeScanner) { - var Html5QrcodeScanner = window.__Html5QrcodeLibrary__.Html5QrcodeScanner; - var Html5Qrcode = window.__Html5QrcodeLibrary__.Html5Qrcode; - var Html5QrcodeSupportedFormats = window.__Html5QrcodeLibrary__.Html5QrcodeSupportedFormats - var Html5QrcodeScannerState = window.__Html5QrcodeLibrary__.Html5QrcodeScannerState; -} \ No newline at end of file diff --git a/libs/webauthn/Attestation/AttestationObject.php b/libs/webauthn/Attestation/AttestationObject.php new file mode 100644 index 0000000..65151ea --- /dev/null +++ b/libs/webauthn/Attestation/AttestationObject.php @@ -0,0 +1,179 @@ +_authenticatorData = new AuthenticatorData($enc['authData']->getBinaryString()); + $this->_attestationFormatName = $enc['fmt']; + + // Format ok? + if (!in_array($this->_attestationFormatName, $allowedFormats)) { + throw new WebAuthnException('invalid atttestation format: ' . $this->_attestationFormatName, WebAuthnException::INVALID_DATA); + } + + + switch ($this->_attestationFormatName) { + case 'android-key': $this->_attestationFormat = new Format\AndroidKey($enc, $this->_authenticatorData); break; + case 'android-safetynet': $this->_attestationFormat = new Format\AndroidSafetyNet($enc, $this->_authenticatorData); break; + case 'apple': $this->_attestationFormat = new Format\Apple($enc, $this->_authenticatorData); break; + case 'fido-u2f': $this->_attestationFormat = new Format\U2f($enc, $this->_authenticatorData); break; + case 'none': $this->_attestationFormat = new Format\None($enc, $this->_authenticatorData); break; + case 'packed': $this->_attestationFormat = new Format\Packed($enc, $this->_authenticatorData); break; + case 'tpm': $this->_attestationFormat = new Format\Tpm($enc, $this->_authenticatorData); break; + default: throw new WebAuthnException('invalid attestation format: ' . $enc['fmt'], WebAuthnException::INVALID_DATA); + } + } + + /** + * returns the attestation format name + * @return string + */ + public function getAttestationFormatName() { + return $this->_attestationFormatName; + } + + /** + * returns the attestation format class + * @return Format\FormatBase + */ + public function getAttestationFormat() { + return $this->_attestationFormat; + } + + /** + * returns the attestation public key in PEM format + * @return AuthenticatorData + */ + public function getAuthenticatorData() { + return $this->_authenticatorData; + } + + /** + * returns the certificate chain as PEM + * @return string|null + */ + public function getCertificateChain() { + return $this->_attestationFormat->getCertificateChain(); + } + + /** + * return the certificate issuer as string + * @return string + */ + public function getCertificateIssuer() { + $pem = $this->getCertificatePem(); + $issuer = ''; + if ($pem) { + $certInfo = \openssl_x509_parse($pem); + if (\is_array($certInfo) && \array_key_exists('issuer', $certInfo) && \is_array($certInfo['issuer'])) { + + $cn = $certInfo['issuer']['CN'] ?? ''; + $o = $certInfo['issuer']['O'] ?? ''; + $ou = $certInfo['issuer']['OU'] ?? ''; + + if ($cn) { + $issuer .= $cn; + } + if ($issuer && ($o || $ou)) { + $issuer .= ' (' . trim($o . ' ' . $ou) . ')'; + } else { + $issuer .= trim($o . ' ' . $ou); + } + } + } + + return $issuer; + } + + /** + * return the certificate subject as string + * @return string + */ + public function getCertificateSubject() { + $pem = $this->getCertificatePem(); + $subject = ''; + if ($pem) { + $certInfo = \openssl_x509_parse($pem); + if (\is_array($certInfo) && \array_key_exists('subject', $certInfo) && \is_array($certInfo['subject'])) { + + $cn = $certInfo['subject']['CN'] ?? ''; + $o = $certInfo['subject']['O'] ?? ''; + $ou = $certInfo['subject']['OU'] ?? ''; + + if ($cn) { + $subject .= $cn; + } + if ($subject && ($o || $ou)) { + $subject .= ' (' . trim($o . ' ' . $ou) . ')'; + } else { + $subject .= trim($o . ' ' . $ou); + } + } + } + + return $subject; + } + + /** + * returns the key certificate in PEM format + * @return string + */ + public function getCertificatePem() { + return $this->_attestationFormat->getCertificatePem(); + } + + /** + * checks validity of the signature + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + public function validateAttestation($clientDataHash) { + return $this->_attestationFormat->validateAttestation($clientDataHash); + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + return $this->_attestationFormat->validateRootCertificate($rootCas); + } + + /** + * checks if the RpId-Hash is valid + * @param string$rpIdHash + * @return bool + */ + public function validateRpIdHash($rpIdHash) { + return $rpIdHash === $this->_authenticatorData->getRpIdHash(); + } +} diff --git a/libs/webauthn/Attestation/AuthenticatorData.php b/libs/webauthn/Attestation/AuthenticatorData.php new file mode 100644 index 0000000..1e1212e --- /dev/null +++ b/libs/webauthn/Attestation/AuthenticatorData.php @@ -0,0 +1,423 @@ +_binary = $binary; + + // Read infos from binary + // https://www.w3.org/TR/webauthn/#sec-authenticator-data + + // RP ID + $this->_rpIdHash = \substr($binary, 0, 32); + + // flags (1 byte) + $flags = \unpack('Cflags', \substr($binary, 32, 1))['flags']; + $this->_flags = $this->_readFlags($flags); + + // signature counter: 32-bit unsigned big-endian integer. + $this->_signCount = \unpack('Nsigncount', \substr($binary, 33, 4))['signcount']; + + $offset = 37; + // https://www.w3.org/TR/webauthn/#sec-attested-credential-data + if ($this->_flags->attestedDataIncluded) { + $this->_attestedCredentialData = $this->_readAttestData($binary, $offset); + } + + if ($this->_flags->extensionDataIncluded) { + $this->_readExtensionData(\substr($binary, $offset)); + } + } + + /** + * Authenticator Attestation Globally Unique Identifier, a unique number + * that identifies the model of the authenticator (not the specific instance + * of the authenticator) + * The aaguid may be 0 if the user is using a old u2f device and/or if + * the browser is using the fido-u2f format. + * @return string + * @throws WebAuthnException + */ + public function getAAGUID() { + if (!($this->_attestedCredentialData instanceof \stdClass)) { + throw new WebAuthnException('credential data not included in authenticator data', WebAuthnException::INVALID_DATA); + } + return $this->_attestedCredentialData->aaguid; + } + + /** + * returns the authenticatorData as binary + * @return string + */ + public function getBinary() { + return $this->_binary; + } + + /** + * returns the credentialId + * @return string + * @throws WebAuthnException + */ + public function getCredentialId() { + if (!($this->_attestedCredentialData instanceof \stdClass)) { + throw new WebAuthnException('credential id not included in authenticator data', WebAuthnException::INVALID_DATA); + } + return $this->_attestedCredentialData->credentialId; + } + + /** + * returns the public key in PEM format + * @return string + */ + public function getPublicKeyPem() { + $der = null; + switch ($this->_attestedCredentialData->credentialPublicKey->kty) { + case self::$_EC2_TYPE: $der = $this->_getEc2Der(); break; + case self::$_RSA_TYPE: $der = $this->_getRsaDer(); break; + default: throw new WebAuthnException('invalid key type', WebAuthnException::INVALID_DATA); + } + + $pem = '-----BEGIN PUBLIC KEY-----' . "\n"; + $pem .= \chunk_split(\base64_encode($der), 64, "\n"); + $pem .= '-----END PUBLIC KEY-----' . "\n"; + return $pem; + } + + /** + * returns the public key in U2F format + * @return string + * @throws WebAuthnException + */ + public function getPublicKeyU2F() { + if (!($this->_attestedCredentialData instanceof \stdClass)) { + throw new WebAuthnException('credential data not included in authenticator data', WebAuthnException::INVALID_DATA); + } + return "\x04" . // ECC uncompressed + $this->_attestedCredentialData->credentialPublicKey->x . + $this->_attestedCredentialData->credentialPublicKey->y; + } + + /** + * returns the SHA256 hash of the relying party id (=hostname) + * @return string + */ + public function getRpIdHash() { + return $this->_rpIdHash; + } + + /** + * returns the sign counter + * @return int + */ + public function getSignCount() { + return $this->_signCount; + } + + /** + * returns true if the user is present + * @return boolean + */ + public function getUserPresent() { + return $this->_flags->userPresent; + } + + /** + * returns true if the user is verified + * @return boolean + */ + public function getUserVerified() { + return $this->_flags->userVerified; + } + + // ----------------------------------------------- + // PRIVATE + // ----------------------------------------------- + + /** + * Returns DER encoded EC2 key + * @return string + */ + private function _getEc2Der() { + return $this->_der_sequence( + $this->_der_sequence( + $this->_der_oid("\x2A\x86\x48\xCE\x3D\x02\x01") . // OID 1.2.840.10045.2.1 ecPublicKey + $this->_der_oid("\x2A\x86\x48\xCE\x3D\x03\x01\x07") // 1.2.840.10045.3.1.7 prime256v1 + ) . + $this->_der_bitString($this->getPublicKeyU2F()) + ); + } + + /** + * Returns DER encoded RSA key + * @return string + */ + private function _getRsaDer() { + return $this->_der_sequence( + $this->_der_sequence( + $this->_der_oid("\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01") . // OID 1.2.840.113549.1.1.1 rsaEncryption + $this->_der_nullValue() + ) . + $this->_der_bitString( + $this->_der_sequence( + $this->_der_unsignedInteger($this->_attestedCredentialData->credentialPublicKey->n) . + $this->_der_unsignedInteger($this->_attestedCredentialData->credentialPublicKey->e) + ) + ) + ); + } + + /** + * reads the flags from flag byte + * @param string $binFlag + * @return \stdClass + */ + private function _readFlags($binFlag) { + $flags = new \stdClass(); + + $flags->bit_0 = !!($binFlag & 1); + $flags->bit_1 = !!($binFlag & 2); + $flags->bit_2 = !!($binFlag & 4); + $flags->bit_3 = !!($binFlag & 8); + $flags->bit_4 = !!($binFlag & 16); + $flags->bit_5 = !!($binFlag & 32); + $flags->bit_6 = !!($binFlag & 64); + $flags->bit_7 = !!($binFlag & 128); + + // named flags + $flags->userPresent = $flags->bit_0; + $flags->userVerified = $flags->bit_2; + $flags->attestedDataIncluded = $flags->bit_6; + $flags->extensionDataIncluded = $flags->bit_7; + return $flags; + } + + /** + * read attested data + * @param string $binary + * @param int $endOffset + * @return \stdClass + * @throws WebAuthnException + */ + private function _readAttestData($binary, &$endOffset) { + $attestedCData = new \stdClass(); + if (\strlen($binary) <= 55) { + throw new WebAuthnException('Attested data should be present but is missing', WebAuthnException::INVALID_DATA); + } + + // The AAGUID of the authenticator + $attestedCData->aaguid = \substr($binary, 37, 16); + + //Byte length L of Credential ID, 16-bit unsigned big-endian integer. + $length = \unpack('nlength', \substr($binary, 53, 2))['length']; + $attestedCData->credentialId = \substr($binary, 55, $length); + + // set end offset + $endOffset = 55 + $length; + + // extract public key + $attestedCData->credentialPublicKey = $this->_readCredentialPublicKey($binary, 55 + $length, $endOffset); + + return $attestedCData; + } + + /** + * reads COSE key-encoded elliptic curve public key in EC2 format + * @param string $binary + * @param int $endOffset + * @return \stdClass + * @throws WebAuthnException + */ + private function _readCredentialPublicKey($binary, $offset, &$endOffset) { + $enc = CborDecoder::decodeInPlace($binary, $offset, $endOffset); + + // COSE key-encoded elliptic curve public key in EC2 format + $credPKey = new \stdClass(); + $credPKey->kty = $enc[self::$_COSE_KTY]; + $credPKey->alg = $enc[self::$_COSE_ALG]; + + switch ($credPKey->alg) { + case self::$_EC2_ES256: $this->_readCredentialPublicKeyES256($credPKey, $enc); break; + case self::$_RSA_RS256: $this->_readCredentialPublicKeyRS256($credPKey, $enc); break; + } + + return $credPKey; + } + + /** + * extract ES256 informations from cose + * @param \stdClass $credPKey + * @param \stdClass $enc + * @throws WebAuthnException + */ + private function _readCredentialPublicKeyES256(&$credPKey, $enc) { + $credPKey->crv = $enc[self::$_COSE_CRV]; + $credPKey->x = $enc[self::$_COSE_X] instanceof ByteBuffer ? $enc[self::$_COSE_X]->getBinaryString() : null; + $credPKey->y = $enc[self::$_COSE_Y] instanceof ByteBuffer ? $enc[self::$_COSE_Y]->getBinaryString() : null; + unset ($enc); + + // Validation + if ($credPKey->kty !== self::$_EC2_TYPE) { + throw new WebAuthnException('public key not in EC2 format', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if ($credPKey->alg !== self::$_EC2_ES256) { + throw new WebAuthnException('signature algorithm not ES256', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if ($credPKey->crv !== self::$_EC2_P256) { + throw new WebAuthnException('curve not P-256', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\strlen($credPKey->x) !== 32) { + throw new WebAuthnException('Invalid X-coordinate', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\strlen($credPKey->y) !== 32) { + throw new WebAuthnException('Invalid Y-coordinate', WebAuthnException::INVALID_PUBLIC_KEY); + } + } + + /** + * extract RS256 informations from COSE + * @param \stdClass $credPKey + * @param \stdClass $enc + * @throws WebAuthnException + */ + private function _readCredentialPublicKeyRS256(&$credPKey, $enc) { + $credPKey->n = $enc[self::$_COSE_N] instanceof ByteBuffer ? $enc[self::$_COSE_N]->getBinaryString() : null; + $credPKey->e = $enc[self::$_COSE_E] instanceof ByteBuffer ? $enc[self::$_COSE_E]->getBinaryString() : null; + unset ($enc); + + // Validation + if ($credPKey->kty !== self::$_RSA_TYPE) { + throw new WebAuthnException('public key not in RSA format', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if ($credPKey->alg !== self::$_RSA_RS256) { + throw new WebAuthnException('signature algorithm not ES256', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\strlen($credPKey->n) !== 256) { + throw new WebAuthnException('Invalid RSA modulus', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\strlen($credPKey->e) !== 3) { + throw new WebAuthnException('Invalid RSA public exponent', WebAuthnException::INVALID_PUBLIC_KEY); + } + + } + + /** + * reads cbor encoded extension data. + * @param string $binary + * @return array + * @throws WebAuthnException + */ + private function _readExtensionData($binary) { + $ext = CborDecoder::decode($binary); + if (!\is_array($ext)) { + throw new WebAuthnException('invalid extension data', WebAuthnException::INVALID_DATA); + } + + return $ext; + } + + + // --------------- + // DER functions + // --------------- + + private function _der_length($len) { + if ($len < 128) { + return \chr($len); + } + $lenBytes = ''; + while ($len > 0) { + $lenBytes = \chr($len % 256) . $lenBytes; + $len = \intdiv($len, 256); + } + return \chr(0x80 | \strlen($lenBytes)) . $lenBytes; + } + + private function _der_sequence($contents) { + return "\x30" . $this->_der_length(\strlen($contents)) . $contents; + } + + private function _der_oid($encoded) { + return "\x06" . $this->_der_length(\strlen($encoded)) . $encoded; + } + + private function _der_bitString($bytes) { + return "\x03" . $this->_der_length(\strlen($bytes) + 1) . "\x00" . $bytes; + } + + private function _der_nullValue() { + return "\x05\x00"; + } + + private function _der_unsignedInteger($bytes) { + $len = \strlen($bytes); + + // Remove leading zero bytes + for ($i = 0; $i < ($len - 1); $i++) { + if (\ord($bytes[$i]) !== 0) { + break; + } + } + if ($i !== 0) { + $bytes = \substr($bytes, $i); + } + + // If most significant bit is set, prefix with another zero to prevent it being seen as negative number + if ((\ord($bytes[0]) & 0x80) !== 0) { + $bytes = "\x00" . $bytes; + } + + return "\x02" . $this->_der_length(\strlen($bytes)) . $bytes; + } +} diff --git a/libs/webauthn/Attestation/Format/AndroidKey.php b/libs/webauthn/Attestation/Format/AndroidKey.php new file mode 100644 index 0000000..4581272 --- /dev/null +++ b/libs/webauthn/Attestation/Format/AndroidKey.php @@ -0,0 +1,96 @@ +_attestationObject['attStmt']; + + if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { + throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { + throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('x5c', $attStmt) || !\is_array($attStmt['x5c']) || \count($attStmt['x5c']) < 1) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + if (!\is_object($attStmt['x5c'][0]) || !($attStmt['x5c'][0] instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_alg = $attStmt['alg']; + $this->_signature = $attStmt['sig']->getBinaryString(); + $this->_x5c = $attStmt['x5c'][0]->getBinaryString(); + + if (count($attStmt['x5c']) > 1) { + for ($i=1; $i_x5c_chain[] = $attStmt['x5c'][$i]->getBinaryString(); + } + unset ($i); + } + } + + + /* + * returns the key certificate in PEM format + * @return string + */ + public function getCertificatePem() { + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash + // using the attestation public key in attestnCert with the algorithm specified in alg. + $dataToVerify = $this->_authenticatorData->getBinary(); + $dataToVerify .= $clientDataHash; + + $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); + + // check certificate + return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } +} + diff --git a/libs/webauthn/Attestation/Format/AndroidSafetyNet.php b/libs/webauthn/Attestation/Format/AndroidSafetyNet.php new file mode 100644 index 0000000..ba0db52 --- /dev/null +++ b/libs/webauthn/Attestation/Format/AndroidSafetyNet.php @@ -0,0 +1,152 @@ +_attestationObject['attStmt']; + + if (!\array_key_exists('ver', $attStmt) || !$attStmt['ver']) { + throw new WebAuthnException('invalid Android Safety Net Format', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('response', $attStmt) || !($attStmt['response'] instanceof ByteBuffer)) { + throw new WebAuthnException('invalid Android Safety Net Format', WebAuthnException::INVALID_DATA); + } + + $response = $attStmt['response']->getBinaryString(); + + // Response is a JWS [RFC7515] object in Compact Serialization. + // JWSs have three segments separated by two period ('.') characters + $parts = \explode('.', $response); + unset ($response); + if (\count($parts) !== 3) { + throw new WebAuthnException('invalid JWS data', WebAuthnException::INVALID_DATA); + } + + $header = $this->_base64url_decode($parts[0]); + $payload = $this->_base64url_decode($parts[1]); + $this->_signature = $this->_base64url_decode($parts[2]); + $this->_signedValue = $parts[0] . '.' . $parts[1]; + unset ($parts); + + $header = \json_decode($header); + $payload = \json_decode($payload); + + if (!($header instanceof \stdClass)) { + throw new WebAuthnException('invalid JWS header', WebAuthnException::INVALID_DATA); + } + if (!($payload instanceof \stdClass)) { + throw new WebAuthnException('invalid JWS payload', WebAuthnException::INVALID_DATA); + } + + if (!isset($header->x5c) || !is_array($header->x5c) || count($header->x5c) === 0) { + throw new WebAuthnException('No X.509 signature in JWS Header', WebAuthnException::INVALID_DATA); + } + + // algorithm + if (!\in_array($header->alg, array('RS256', 'ES256'))) { + throw new WebAuthnException('invalid JWS algorithm ' . $header->alg, WebAuthnException::INVALID_DATA); + } + + $this->_x5c = \base64_decode($header->x5c[0]); + $this->_payload = $payload; + + if (count($header->x5c) > 1) { + for ($i=1; $ix5c); $i++) { + $this->_x5c_chain[] = \base64_decode($header->x5c[$i]); + } + unset ($i); + } + } + + /** + * ctsProfileMatch: A stricter verdict of device integrity. + * If the value of ctsProfileMatch is true, then the profile of the device running your app matches + * the profile of a device that has passed Android compatibility testing and + * has been approved as a Google-certified Android device. + * @return bool + */ + public function ctsProfileMatch() { + return isset($this->_payload->ctsProfileMatch) ? !!$this->_payload->ctsProfileMatch : false; + } + + + /* + * returns the key certificate in PEM format + * @return string + */ + public function getCertificatePem() { + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + // Verify that the nonce in the response is identical to the Base64 encoding + // of the SHA-256 hash of the concatenation of authenticatorData and clientDataHash. + if (empty($this->_payload->nonce) || $this->_payload->nonce !== \base64_encode(\hash('SHA256', $this->_authenticatorData->getBinary() . $clientDataHash, true))) { + throw new WebAuthnException('invalid nonce in JWS payload', WebAuthnException::INVALID_DATA); + } + + // Verify that attestationCert is issued to the hostname "attest.android.com" + $certInfo = \openssl_x509_parse($this->getCertificatePem()); + if (!\is_array($certInfo) || ($certInfo['subject']['CN'] ?? '') !== 'attest.android.com') { + throw new WebAuthnException('invalid certificate CN in JWS (' . ($certInfo['subject']['CN'] ?? '-'). ')', WebAuthnException::INVALID_DATA); + } + + // Verify that the basicIntegrity attribute in the payload of response is true. + if (empty($this->_payload->basicIntegrity)) { + throw new WebAuthnException('invalid basicIntegrity in payload', WebAuthnException::INVALID_DATA); + } + + // check certificate + return \openssl_verify($this->_signedValue, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1; + } + + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } + + + /** + * decode base64 url + * @param string $data + * @return string + */ + private function _base64url_decode($data) { + return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4)); + } +} + diff --git a/libs/webauthn/Attestation/Format/Apple.php b/libs/webauthn/Attestation/Format/Apple.php new file mode 100644 index 0000000..e4f38e0 --- /dev/null +++ b/libs/webauthn/Attestation/Format/Apple.php @@ -0,0 +1,139 @@ +_attestationObject['attStmt']; + + + // certificate for validation + if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { + + // The attestation certificate attestnCert MUST be the first element in the array + $attestnCert = array_shift($attStmt['x5c']); + + if (!($attestnCert instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_x5c = $attestnCert->getBinaryString(); + + // certificate chain + foreach ($attStmt['x5c'] as $chain) { + if ($chain instanceof ByteBuffer) { + $this->_x5c_chain[] = $chain->getBinaryString(); + } + } + } else { + throw new WebAuthnException('invalid Apple attestation statement: missing x5c', WebAuthnException::INVALID_DATA); + } + } + + + /* + * returns the key certificate in PEM format + * @return string|null + */ + public function getCertificatePem() { + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + return $this->_validateOverX5c($clientDataHash); + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } + + /** + * validate if x5c is present + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + protected function _validateOverX5c($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Concatenate authenticatorData and clientDataHash to form nonceToHash. + $nonceToHash = $this->_authenticatorData->getBinary(); + $nonceToHash .= $clientDataHash; + + // Perform SHA-256 hash of nonceToHash to produce nonce + $nonce = hash('SHA256', $nonceToHash, true); + + $credCert = openssl_x509_read($this->getCertificatePem()); + if ($credCert === false) { + throw new WebAuthnException('invalid x5c certificate: ' . \openssl_error_string(), WebAuthnException::INVALID_DATA); + } + + $keyData = openssl_pkey_get_details(openssl_pkey_get_public($credCert)); + $key = is_array($keyData) && array_key_exists('key', $keyData) ? $keyData['key'] : null; + + + // Verify that nonce equals the value of the extension with OID ( 1.2.840.113635.100.8.2 ) in credCert. + $parsedCredCert = openssl_x509_parse($credCert); + $nonceExtension = $parsedCredCert['extensions']['1.2.840.113635.100.8.2'] ?? ''; + + // nonce padded by ASN.1 string: 30 24 A1 22 04 20 + // 30 — type tag indicating sequence + // 24 — 36 byte following + // A1 — Enumerated [1] + // 22 — 34 byte following + // 04 — type tag indicating octet string + // 20 — 32 byte following + + $asn1Padding = "\x30\x24\xA1\x22\x04\x20"; + if (substr($nonceExtension, 0, strlen($asn1Padding)) === $asn1Padding) { + $nonceExtension = substr($nonceExtension, strlen($asn1Padding)); + } + + if ($nonceExtension !== $nonce) { + throw new WebAuthnException('nonce doesn\'t equal the value of the extension with OID 1.2.840.113635.100.8.2', WebAuthnException::INVALID_DATA); + } + + // Verify that the credential public key equals the Subject Public Key of credCert. + $authKeyData = openssl_pkey_get_details(openssl_pkey_get_public($this->_authenticatorData->getPublicKeyPem())); + $authKey = is_array($authKeyData) && array_key_exists('key', $authKeyData) ? $authKeyData['key'] : null; + + if ($key === null || $key !== $authKey) { + throw new WebAuthnException('credential public key doesn\'t equal the Subject Public Key of credCert', WebAuthnException::INVALID_DATA); + } + + return true; + } + +} + diff --git a/libs/webauthn/Attestation/Format/FormatBase.php b/libs/webauthn/Attestation/Format/FormatBase.php new file mode 100644 index 0000000..eed916b --- /dev/null +++ b/libs/webauthn/Attestation/Format/FormatBase.php @@ -0,0 +1,193 @@ +_attestationObject = $AttestionObject; + $this->_authenticatorData = $authenticatorData; + } + + /** + * + */ + public function __destruct() { + // delete X.509 chain certificate file after use + if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) { + \unlink($this->_x5c_tempFile); + } + } + + /** + * returns the certificate chain in PEM format + * @return string|null + */ + public function getCertificateChain() { + if ($this->_x5c_tempFile && \is_file($this->_x5c_tempFile)) { + return \file_get_contents($this->_x5c_tempFile); + } + return null; + } + + /** + * returns the key X.509 certificate in PEM format + * @return string + */ + public function getCertificatePem() { + // need to be overwritten + return null; + } + + /** + * checks validity of the signature + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + public function validateAttestation($clientDataHash) { + // need to be overwritten + return false; + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + // need to be overwritten + return false; + } + + + /** + * create a PEM encoded certificate with X.509 binary data + * @param string $x5c + * @return string + */ + protected function _createCertificatePem($x5c) { + $pem = '-----BEGIN CERTIFICATE-----' . "\n"; + $pem .= \chunk_split(\base64_encode($x5c), 64, "\n"); + $pem .= '-----END CERTIFICATE-----' . "\n"; + return $pem; + } + + /** + * creates a PEM encoded chain file + * @return type + */ + protected function _createX5cChainFile() { + $content = ''; + if (\is_array($this->_x5c_chain) && \count($this->_x5c_chain) > 0) { + foreach ($this->_x5c_chain as $x5c) { + $certInfo = \openssl_x509_parse($this->_createCertificatePem($x5c)); + + // check if certificate is self signed + if (\is_array($certInfo) && \is_array($certInfo['issuer']) && \is_array($certInfo['subject'])) { + $selfSigned = false; + + $subjectKeyIdentifier = $certInfo['extensions']['subjectKeyIdentifier'] ?? null; + $authorityKeyIdentifier = $certInfo['extensions']['authorityKeyIdentifier'] ?? null; + + if ($authorityKeyIdentifier && substr($authorityKeyIdentifier, 0, 6) === 'keyid:') { + $authorityKeyIdentifier = substr($authorityKeyIdentifier, 6); + } + if ($subjectKeyIdentifier && substr($subjectKeyIdentifier, 0, 6) === 'keyid:') { + $subjectKeyIdentifier = substr($subjectKeyIdentifier, 6); + } + + if (($subjectKeyIdentifier && !$authorityKeyIdentifier) || ($authorityKeyIdentifier && $authorityKeyIdentifier === $subjectKeyIdentifier)) { + $selfSigned = true; + } + + if (!$selfSigned) { + $content .= "\n" . $this->_createCertificatePem($x5c) . "\n"; + } + } + } + } + + if ($content) { + $this->_x5c_tempFile = \sys_get_temp_dir() . '/x5c_chain_' . \base_convert(\rand(), 10, 36) . '.pem'; + if (\file_put_contents($this->_x5c_tempFile, $content) !== false) { + return $this->_x5c_tempFile; + } + } + + return null; + } + + + /** + * returns the name and openssl key for provided cose number. + * @param int $coseNumber + * @return \stdClass|null + */ + protected function _getCoseAlgorithm($coseNumber) { + // https://www.iana.org/assignments/cose/cose.xhtml#algorithms + $coseAlgorithms = array( + array( + 'hash' => 'SHA1', + 'openssl' => OPENSSL_ALGO_SHA1, + 'cose' => array( + -65535 // RS1 + )), + + array( + 'hash' => 'SHA256', + 'openssl' => OPENSSL_ALGO_SHA256, + 'cose' => array( + -257, // RS256 + -37, // PS256 + -7, // ES256 + 5 // HMAC256 + )), + + array( + 'hash' => 'SHA384', + 'openssl' => OPENSSL_ALGO_SHA384, + 'cose' => array( + -258, // RS384 + -38, // PS384 + -35, // ES384 + 6 // HMAC384 + )), + + array( + 'hash' => 'SHA512', + 'openssl' => OPENSSL_ALGO_SHA512, + 'cose' => array( + -259, // RS512 + -39, // PS512 + -36, // ES512 + 7 // HMAC512 + )) + ); + + foreach ($coseAlgorithms as $coseAlgorithm) { + if (\in_array($coseNumber, $coseAlgorithm['cose'], true)) { + $return = new \stdClass(); + $return->hash = $coseAlgorithm['hash']; + $return->openssl = $coseAlgorithm['openssl']; + return $return; + } + } + + return null; + } +} diff --git a/libs/webauthn/Attestation/Format/None.php b/libs/webauthn/Attestation/Format/None.php new file mode 100644 index 0000000..ba95e40 --- /dev/null +++ b/libs/webauthn/Attestation/Format/None.php @@ -0,0 +1,41 @@ +_attestationObject['attStmt']; + + if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { + throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { + throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); + } + + $this->_alg = $attStmt['alg']; + $this->_signature = $attStmt['sig']->getBinaryString(); + + // certificate for validation + if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { + + // The attestation certificate attestnCert MUST be the first element in the array + $attestnCert = array_shift($attStmt['x5c']); + + if (!($attestnCert instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_x5c = $attestnCert->getBinaryString(); + + // certificate chain + foreach ($attStmt['x5c'] as $chain) { + if ($chain instanceof ByteBuffer) { + $this->_x5c_chain[] = $chain->getBinaryString(); + } + } + } + } + + + /* + * returns the key certificate in PEM format + * @return string|null + */ + public function getCertificatePem() { + if (!$this->_x5c) { + return null; + } + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + if ($this->_x5c) { + return $this->_validateOverX5c($clientDataHash); + } else { + return $this->_validateSelfAttestation($clientDataHash); + } + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + if (!$this->_x5c) { + return false; + } + + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } + + /** + * validate if x5c is present + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + protected function _validateOverX5c($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash + // using the attestation public key in attestnCert with the algorithm specified in alg. + $dataToVerify = $this->_authenticatorData->getBinary(); + $dataToVerify .= $clientDataHash; + + $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); + + // check certificate + return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; + } + + /** + * validate if self attestation is in use + * @param string $clientDataHash + * @return bool + */ + protected function _validateSelfAttestation($clientDataHash) { + // Verify that sig is a valid signature over the concatenation of authenticatorData and clientDataHash + // using the credential public key with alg. + $dataToVerify = $this->_authenticatorData->getBinary(); + $dataToVerify .= $clientDataHash; + + $publicKey = $this->_authenticatorData->getPublicKeyPem(); + + // check certificate + return \openssl_verify($dataToVerify, $this->_signature, $publicKey, OPENSSL_ALGO_SHA256) === 1; + } +} + diff --git a/libs/webauthn/Attestation/Format/Tpm.php b/libs/webauthn/Attestation/Format/Tpm.php new file mode 100644 index 0000000..338cd45 --- /dev/null +++ b/libs/webauthn/Attestation/Format/Tpm.php @@ -0,0 +1,180 @@ +_attestationObject['attStmt']; + + if (!\array_key_exists('ver', $attStmt) || $attStmt['ver'] !== '2.0') { + throw new WebAuthnException('invalid tpm version: ' . $attStmt['ver'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('alg', $attStmt) || $this->_getCoseAlgorithm($attStmt['alg']) === null) { + throw new WebAuthnException('unsupported alg: ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { + throw new WebAuthnException('signature not found', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('certInfo', $attStmt) || !\is_object($attStmt['certInfo']) || !($attStmt['certInfo'] instanceof ByteBuffer)) { + throw new WebAuthnException('certInfo not found', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('pubArea', $attStmt) || !\is_object($attStmt['pubArea']) || !($attStmt['pubArea'] instanceof ByteBuffer)) { + throw new WebAuthnException('pubArea not found', WebAuthnException::INVALID_DATA); + } + + $this->_alg = $attStmt['alg']; + $this->_signature = $attStmt['sig']->getBinaryString(); + $this->_certInfo = $attStmt['certInfo']; + $this->_pubArea = $attStmt['pubArea']; + + // certificate for validation + if (\array_key_exists('x5c', $attStmt) && \is_array($attStmt['x5c']) && \count($attStmt['x5c']) > 0) { + + // The attestation certificate attestnCert MUST be the first element in the array + $attestnCert = array_shift($attStmt['x5c']); + + if (!($attestnCert instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_x5c = $attestnCert->getBinaryString(); + + // certificate chain + foreach ($attStmt['x5c'] as $chain) { + if ($chain instanceof ByteBuffer) { + $this->_x5c_chain[] = $chain->getBinaryString(); + } + } + + } else { + throw new WebAuthnException('no x5c certificate found', WebAuthnException::INVALID_DATA); + } + } + + + /* + * returns the key certificate in PEM format + * @return string|null + */ + public function getCertificatePem() { + if (!$this->_x5c) { + return null; + } + return $this->_createCertificatePem($this->_x5c); + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + return $this->_validateOverX5c($clientDataHash); + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + if (!$this->_x5c) { + return false; + } + + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } + + /** + * validate if x5c is present + * @param string $clientDataHash + * @return bool + * @throws WebAuthnException + */ + protected function _validateOverX5c($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Concatenate authenticatorData and clientDataHash to form attToBeSigned. + $attToBeSigned = $this->_authenticatorData->getBinary(); + $attToBeSigned .= $clientDataHash; + + // Validate that certInfo is valid: + + // Verify that magic is set to TPM_GENERATED_VALUE. + if ($this->_certInfo->getBytes(0, 4) !== $this->_TPM_GENERATED_VALUE) { + throw new WebAuthnException('tpm magic not TPM_GENERATED_VALUE', WebAuthnException::INVALID_DATA); + } + + // Verify that type is set to TPM_ST_ATTEST_CERTIFY. + if ($this->_certInfo->getBytes(4, 2) !== $this->_TPM_ST_ATTEST_CERTIFY) { + throw new WebAuthnException('tpm type not TPM_ST_ATTEST_CERTIFY', WebAuthnException::INVALID_DATA); + } + + $offset = 6; + $qualifiedSigner = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset); + $extraData = $this->_tpmReadLengthPrefixed($this->_certInfo, $offset); + $coseAlg = $this->_getCoseAlgorithm($this->_alg); + + // Verify that extraData is set to the hash of attToBeSigned using the hash algorithm employed in "alg". + if ($extraData->getBinaryString() !== \hash($coseAlg->hash, $attToBeSigned, true)) { + throw new WebAuthnException('certInfo:extraData not hash of attToBeSigned', WebAuthnException::INVALID_DATA); + } + + // Verify the sig is a valid signature over certInfo using the attestation + // public key in aikCert with the algorithm specified in alg. + return \openssl_verify($this->_certInfo->getBinaryString(), $this->_signature, $publicKey, $coseAlg->openssl) === 1; + } + + + /** + * returns next part of ByteBuffer + * @param ByteBuffer $buffer + * @param int $offset + * @return ByteBuffer + */ + protected function _tpmReadLengthPrefixed(ByteBuffer $buffer, &$offset) { + $len = $buffer->getUint16Val($offset); + $data = $buffer->getBytes($offset + 2, $len); + $offset += (2 + $len); + + return new ByteBuffer($data); + } + +} + diff --git a/libs/webauthn/Attestation/Format/U2f.php b/libs/webauthn/Attestation/Format/U2f.php new file mode 100644 index 0000000..2b51ba8 --- /dev/null +++ b/libs/webauthn/Attestation/Format/U2f.php @@ -0,0 +1,93 @@ +_attestationObject['attStmt']; + + if (\array_key_exists('alg', $attStmt) && $attStmt['alg'] !== $this->_alg) { + throw new WebAuthnException('u2f only accepts algorithm -7 ("ES256"), but got ' . $attStmt['alg'], WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('sig', $attStmt) || !\is_object($attStmt['sig']) || !($attStmt['sig'] instanceof ByteBuffer)) { + throw new WebAuthnException('no signature found', WebAuthnException::INVALID_DATA); + } + + if (!\array_key_exists('x5c', $attStmt) || !\is_array($attStmt['x5c']) || \count($attStmt['x5c']) !== 1) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + if (!\is_object($attStmt['x5c'][0]) || !($attStmt['x5c'][0] instanceof ByteBuffer)) { + throw new WebAuthnException('invalid x5c certificate', WebAuthnException::INVALID_DATA); + } + + $this->_signature = $attStmt['sig']->getBinaryString(); + $this->_x5c = $attStmt['x5c'][0]->getBinaryString(); + } + + + /* + * returns the key certificate in PEM format + * @return string + */ + public function getCertificatePem() { + $pem = '-----BEGIN CERTIFICATE-----' . "\n"; + $pem .= \chunk_split(\base64_encode($this->_x5c), 64, "\n"); + $pem .= '-----END CERTIFICATE-----' . "\n"; + return $pem; + } + + /** + * @param string $clientDataHash + */ + public function validateAttestation($clientDataHash) { + $publicKey = \openssl_pkey_get_public($this->getCertificatePem()); + + if ($publicKey === false) { + throw new WebAuthnException('invalid public key: ' . \openssl_error_string(), WebAuthnException::INVALID_PUBLIC_KEY); + } + + // Let verificationData be the concatenation of (0x00 || rpIdHash || clientDataHash || credentialId || publicKeyU2F) + $dataToVerify = "\x00"; + $dataToVerify .= $this->_authenticatorData->getRpIdHash(); + $dataToVerify .= $clientDataHash; + $dataToVerify .= $this->_authenticatorData->getCredentialId(); + $dataToVerify .= $this->_authenticatorData->getPublicKeyU2F(); + + $coseAlgorithm = $this->_getCoseAlgorithm($this->_alg); + + // check certificate + return \openssl_verify($dataToVerify, $this->_signature, $publicKey, $coseAlgorithm->openssl) === 1; + } + + /** + * validates the certificate against root certificates + * @param array $rootCas + * @return boolean + * @throws WebAuthnException + */ + public function validateRootCertificate($rootCas) { + $chainC = $this->_createX5cChainFile(); + if ($chainC) { + $rootCas[] = $chainC; + } + + $v = \openssl_x509_checkpurpose($this->getCertificatePem(), -1, $rootCas); + if ($v === -1) { + throw new WebAuthnException('error on validating root certificate: ' . \openssl_error_string(), WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + return $v; + } +} diff --git a/libs/webauthn/Binary/ByteBuffer.php b/libs/webauthn/Binary/ByteBuffer.php new file mode 100644 index 0000000..861ed60 --- /dev/null +++ b/libs/webauthn/Binary/ByteBuffer.php @@ -0,0 +1,300 @@ +_data = (string)$binaryData; + $this->_length = \strlen($binaryData); + } + + + // ----------------------- + // PUBLIC STATIC + // ----------------------- + + /** + * create a ByteBuffer from a base64 url encoded string + * @param string $base64url + * @return ByteBuffer + */ + public static function fromBase64Url($base64url): ByteBuffer { + $bin = self::_base64url_decode($base64url); + if ($bin === false) { + throw new WebAuthnException('ByteBuffer: Invalid base64 url string', WebAuthnException::BYTEBUFFER); + } + return new ByteBuffer($bin); + } + + /** + * create a ByteBuffer from a base64 url encoded string + * @param string $hex + * @return ByteBuffer + */ + public static function fromHex($hex): ByteBuffer { + $bin = \hex2bin($hex); + if ($bin === false) { + throw new WebAuthnException('ByteBuffer: Invalid hex string', WebAuthnException::BYTEBUFFER); + } + return new ByteBuffer($bin); + } + + /** + * create a random ByteBuffer + * @param string $length + * @return ByteBuffer + */ + public static function randomBuffer($length): ByteBuffer { + if (\function_exists('random_bytes')) { // >PHP 7.0 + return new ByteBuffer(\random_bytes($length)); + + } else if (\function_exists('openssl_random_pseudo_bytes')) { + return new ByteBuffer(\openssl_random_pseudo_bytes($length)); + + } else { + throw new WebAuthnException('ByteBuffer: cannot generate random bytes', WebAuthnException::BYTEBUFFER); + } + } + + // ----------------------- + // PUBLIC + // ----------------------- + + public function getBytes($offset, $length): string { + if ($offset < 0 || $length < 0 || ($offset + $length > $this->_length)) { + throw new WebAuthnException('ByteBuffer: Invalid offset or length', WebAuthnException::BYTEBUFFER); + } + return \substr($this->_data, $offset, $length); + } + + public function getByteVal($offset): int { + if ($offset < 0 || $offset >= $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + return \ord(\substr($this->_data, $offset, 1)); + } + + public function getJson($jsonFlags=0) { + $data = \json_decode($this->getBinaryString(), null, 512, $jsonFlags); + if (\json_last_error() !== JSON_ERROR_NONE) { + throw new WebAuthnException(\json_last_error_msg(), WebAuthnException::BYTEBUFFER); + } + return $data; + } + + public function getLength(): int { + return $this->_length; + } + + public function getUint16Val($offset) { + if ($offset < 0 || ($offset + 2) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + return unpack('n', $this->_data, $offset)[1]; + } + + public function getUint32Val($offset) { + if ($offset < 0 || ($offset + 4) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + $val = unpack('N', $this->_data, $offset)[1]; + + // Signed integer overflow causes signed negative numbers + if ($val < 0) { + throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER); + } + return $val; + } + + public function getUint64Val($offset) { + if (PHP_INT_SIZE < 8) { + throw new WebAuthnException('ByteBuffer: 64-bit values not supported by this system', WebAuthnException::BYTEBUFFER); + } + if ($offset < 0 || ($offset + 8) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + $val = unpack('J', $this->_data, $offset)[1]; + + // Signed integer overflow causes signed negative numbers + if ($val < 0) { + throw new WebAuthnException('ByteBuffer: Value out of integer range.', WebAuthnException::BYTEBUFFER); + } + + return $val; + } + + public function getHalfFloatVal($offset) { + //FROM spec pseudo decode_half(unsigned char *halfp) + $half = $this->getUint16Val($offset); + + $exp = ($half >> 10) & 0x1f; + $mant = $half & 0x3ff; + + if ($exp === 0) { + $val = $mant * (2 ** -24); + } elseif ($exp !== 31) { + $val = ($mant + 1024) * (2 ** ($exp - 25)); + } else { + $val = ($mant === 0) ? INF : NAN; + } + + return ($half & 0x8000) ? -$val : $val; + } + + public function getFloatVal($offset) { + if ($offset < 0 || ($offset + 4) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + return unpack('G', $this->_data, $offset)[1]; + } + + public function getDoubleVal($offset) { + if ($offset < 0 || ($offset + 8) > $this->_length) { + throw new WebAuthnException('ByteBuffer: Invalid offset', WebAuthnException::BYTEBUFFER); + } + return unpack('E', $this->_data, $offset)[1]; + } + + /** + * @return string + */ + public function getBinaryString(): string { + return $this->_data; + } + + /** + * @param string|ByteBuffer $buffer + * @return bool + */ + public function equals($buffer): bool { + if (is_object($buffer) && $buffer instanceof ByteBuffer) { + return $buffer->getBinaryString() === $this->getBinaryString(); + + } else if (is_string($buffer)) { + return $buffer === $this->getBinaryString(); + } + + return false; + } + + /** + * @return string + */ + public function getHex(): string { + return \bin2hex($this->_data); + } + + /** + * @return bool + */ + public function isEmpty(): bool { + return $this->_length === 0; + } + + + /** + * jsonSerialize interface + * return binary data in RFC 1342-Like serialized string + * @return string + */ + public function jsonSerialize(): string { + if (ByteBuffer::$useBase64UrlEncoding) { + return self::_base64url_encode($this->_data); + + } else { + return '=?BINARY?B?' . \base64_encode($this->_data) . '?='; + } + } + + /** + * Serializable-Interface + * @return string + */ + public function serialize(): string { + return \serialize($this->_data); + } + + /** + * Serializable-Interface + * @param string $serialized + */ + public function unserialize($serialized) { + $this->_data = \unserialize($serialized); + $this->_length = \strlen($this->_data); + } + + /** + * (PHP 8 deprecates Serializable-Interface) + * @return array + */ + public function __serialize(): array { + return [ + 'data' => \serialize($this->_data) + ]; + } + + /** + * object to string + * @return string + */ + public function __toString(): string { + return $this->getHex(); + } + + /** + * (PHP 8 deprecates Serializable-Interface) + * @param array $data + * @return void + */ + public function __unserialize($data) { + if ($data && isset($data['data'])) { + $this->_data = \unserialize($data['data']); + $this->_length = \strlen($this->_data); + } + } + + // ----------------------- + // PROTECTED STATIC + // ----------------------- + + /** + * base64 url decoding + * @param string $data + * @return string + */ + protected static function _base64url_decode($data): string { + return \base64_decode(\strtr($data, '-_', '+/') . \str_repeat('=', 3 - (3 + \strlen($data)) % 4)); + } + + /** + * base64 url encoding + * @param string $data + * @return string + */ + protected static function _base64url_encode($data): string { + return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '='); + } +} diff --git a/libs/webauthn/CBOR/CborDecoder.php b/libs/webauthn/CBOR/CborDecoder.php new file mode 100644 index 0000000..e6b5427 --- /dev/null +++ b/libs/webauthn/CBOR/CborDecoder.php @@ -0,0 +1,220 @@ +getLength()) { + throw new WebAuthnException('Unused bytes after data item.', WebAuthnException::CBOR); + } + return $result; + } + + /** + * @param ByteBuffer|string $bufOrBin + * @param int $startOffset + * @param int|null $endOffset + * @return mixed + */ + public static function decodeInPlace($bufOrBin, $startOffset, &$endOffset = null) { + $buf = $bufOrBin instanceof ByteBuffer ? $bufOrBin : new ByteBuffer($bufOrBin); + + $offset = $startOffset; + $data = self::_parseItem($buf, $offset); + $endOffset = $offset; + return $data; + } + + // --------------------- + // protected + // --------------------- + + /** + * @param ByteBuffer $buf + * @param int $offset + * @return mixed + */ + protected static function _parseItem(ByteBuffer $buf, &$offset) { + $first = $buf->getByteVal($offset++); + $type = $first >> 5; + $val = $first & 0b11111; + + if ($type === self::CBOR_MAJOR_FLOAT_SIMPLE) { + return self::_parseFloatSimple($val, $buf, $offset); + } + + $val = self::_parseExtraLength($val, $buf, $offset); + + return self::_parseItemData($type, $val, $buf, $offset); + } + + protected static function _parseFloatSimple($val, ByteBuffer $buf, &$offset) { + switch ($val) { + case 24: + $val = $buf->getByteVal($offset); + $offset++; + return self::_parseSimple($val); + + case 25: + $floatValue = $buf->getHalfFloatVal($offset); + $offset += 2; + return $floatValue; + + case 26: + $floatValue = $buf->getFloatVal($offset); + $offset += 4; + return $floatValue; + + case 27: + $floatValue = $buf->getDoubleVal($offset); + $offset += 8; + return $floatValue; + + case 28: + case 29: + case 30: + throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR); + + case 31: + throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR); + } + + return self::_parseSimple($val); + } + + /** + * @param int $val + * @return mixed + * @throws WebAuthnException + */ + protected static function _parseSimple($val) { + if ($val === 20) { + return false; + } + if ($val === 21) { + return true; + } + if ($val === 22) { + return null; + } + throw new WebAuthnException(sprintf('Unsupported simple value %d.', $val), WebAuthnException::CBOR); + } + + protected static function _parseExtraLength($val, ByteBuffer $buf, &$offset) { + switch ($val) { + case 24: + $val = $buf->getByteVal($offset); + $offset++; + break; + + case 25: + $val = $buf->getUint16Val($offset); + $offset += 2; + break; + + case 26: + $val = $buf->getUint32Val($offset); + $offset += 4; + break; + + case 27: + $val = $buf->getUint64Val($offset); + $offset += 8; + break; + + case 28: + case 29: + case 30: + throw new WebAuthnException('Reserved value used.', WebAuthnException::CBOR); + + case 31: + throw new WebAuthnException('Indefinite length is not supported.', WebAuthnException::CBOR); + } + + return $val; + } + + protected static function _parseItemData($type, $val, ByteBuffer $buf, &$offset) { + switch ($type) { + case self::CBOR_MAJOR_UNSIGNED_INT: // uint + return $val; + + case self::CBOR_MAJOR_NEGATIVE_INT: + return -1 - $val; + + case self::CBOR_MAJOR_BYTE_STRING: + $data = $buf->getBytes($offset, $val); + $offset += $val; + return new ByteBuffer($data); // bytes + + case self::CBOR_MAJOR_TEXT_STRING: + $data = $buf->getBytes($offset, $val); + $offset += $val; + return $data; // UTF-8 + + case self::CBOR_MAJOR_ARRAY: + return self::_parseArray($buf, $offset, $val); + + case self::CBOR_MAJOR_MAP: + return self::_parseMap($buf, $offset, $val); + + case self::CBOR_MAJOR_TAG: + return self::_parseItem($buf, $offset); // 1 embedded data item + } + + // This should never be reached + throw new WebAuthnException(sprintf('Unknown major type %d.', $type), WebAuthnException::CBOR); + } + + protected static function _parseMap(ByteBuffer $buf, &$offset, $count) { + $map = array(); + + for ($i = 0; $i < $count; $i++) { + $mapKey = self::_parseItem($buf, $offset); + $mapVal = self::_parseItem($buf, $offset); + + if (!\is_int($mapKey) && !\is_string($mapKey)) { + throw new WebAuthnException('Can only use strings or integers as map keys', WebAuthnException::CBOR); + } + + $map[$mapKey] = $mapVal; // todo dup + } + return $map; + } + + protected static function _parseArray(ByteBuffer $buf, &$offset, $count) { + $arr = array(); + for ($i = 0; $i < $count; $i++) { + $arr[] = self::_parseItem($buf, $offset); + } + + return $arr; + } +} diff --git a/libs/webauthn/WebAuthn.php b/libs/webauthn/WebAuthn.php new file mode 100644 index 0000000..c10d5c4 --- /dev/null +++ b/libs/webauthn/WebAuthn.php @@ -0,0 +1,626 @@ +_rpName = $rpName; + $this->_rpId = $rpId; + $this->_rpIdHash = \hash('sha256', $rpId, true); + ByteBuffer::$useBase64UrlEncoding = !!$useBase64UrlEncoding; + $supportedFormats = array('android-key', 'android-safetynet', 'apple', 'fido-u2f', 'none', 'packed', 'tpm'); + + if (!\function_exists('\openssl_open')) { + throw new WebAuthnException('OpenSSL-Module not installed');; + } + + if (!\in_array('SHA256', \array_map('\strtoupper', \openssl_get_md_methods()))) { + throw new WebAuthnException('SHA256 not supported by this openssl installation.'); + } + + // default: all format + if (!is_array($allowedFormats)) { + $allowedFormats = $supportedFormats; + } + $this->_formats = $allowedFormats; + + // validate formats + $invalidFormats = \array_diff($this->_formats, $supportedFormats); + if (!$this->_formats || $invalidFormats) { + throw new WebAuthnException('invalid formats on construct: ' . implode(', ', $invalidFormats)); + } + } + + /** + * add a root certificate to verify new registrations + * @param string $path file path of / directory with root certificates + * @param array|null $certFileExtensions if adding a direction, all files with provided extension are added. default: pem, crt, cer, der + */ + public function addRootCertificates($path, $certFileExtensions=null) { + if (!\is_array($this->_caFiles)) { + $this->_caFiles = array(); + } + if ($certFileExtensions === null) { + $certFileExtensions = array('pem', 'crt', 'cer', 'der'); + } + $path = \rtrim(\trim($path), '\\/'); + if (\is_dir($path)) { + foreach (\scandir($path) as $ca) { + if (\is_file($path . DIRECTORY_SEPARATOR . $ca) && \in_array(\strtolower(\pathinfo($ca, PATHINFO_EXTENSION)), $certFileExtensions)) { + $this->addRootCertificates($path . DIRECTORY_SEPARATOR . $ca); + } + } + } else if (\is_file($path) && !\in_array(\realpath($path), $this->_caFiles)) { + $this->_caFiles[] = \realpath($path); + } + } + + /** + * Returns the generated challenge to save for later validation + * @return ByteBuffer + */ + public function getChallenge() { + return $this->_challenge; + } + + /** + * generates the object for a key registration + * provide this data to navigator.credentials.create + * @param string $userId + * @param string $userName + * @param string $userDisplayName + * @param int $timeout timeout in seconds + * @param bool|string $requireResidentKey 'required', if the key should be stored by the authentication device + * Valid values: + * true = required + * false = preferred + * string 'required' 'preferred' 'discouraged' + * @param bool|string $requireUserVerification indicates that you require user verification and will fail the operation + * if the response does not have the UV flag set. + * Valid values: + * true = required + * false = preferred + * string 'required' 'preferred' 'discouraged' + * @param bool|null $crossPlatformAttachment true for cross-platform devices (eg. fido usb), + * false for platform devices (eg. windows hello, android safetynet), + * null for both + * @param array $excludeCredentialIds a array of ids, which are already registered, to prevent re-registration + * @return \stdClass + */ + public function getCreateArgs($userId, $userName, $userDisplayName, $timeout=20, $requireResidentKey=false, $requireUserVerification=false, $crossPlatformAttachment=null, $excludeCredentialIds=array()) { + + $args = new \stdClass(); + $args->publicKey = new \stdClass(); + + // relying party + $args->publicKey->rp = new \stdClass(); + $args->publicKey->rp->name = $this->_rpName; + $args->publicKey->rp->id = $this->_rpId; + + $args->publicKey->authenticatorSelection = new \stdClass(); + $args->publicKey->authenticatorSelection->userVerification = 'preferred'; + + // validate User Verification Requirement + if (\is_bool($requireUserVerification)) { + $args->publicKey->authenticatorSelection->userVerification = $requireUserVerification ? 'required' : 'preferred'; + + } else if (\is_string($requireUserVerification) && \in_array(\strtolower($requireUserVerification), ['required', 'preferred', 'discouraged'])) { + $args->publicKey->authenticatorSelection->userVerification = \strtolower($requireUserVerification); + } + + // validate Resident Key Requirement + if (\is_bool($requireResidentKey) && $requireResidentKey) { + $args->publicKey->authenticatorSelection->requireResidentKey = true; + $args->publicKey->authenticatorSelection->residentKey = 'required'; + + } else if (\is_string($requireResidentKey) && \in_array(\strtolower($requireResidentKey), ['required', 'preferred', 'discouraged'])) { + $requireResidentKey = \strtolower($requireResidentKey); + $args->publicKey->authenticatorSelection->residentKey = $requireResidentKey; + $args->publicKey->authenticatorSelection->requireResidentKey = $requireResidentKey === 'required'; + } + + // filte authenticators attached with the specified authenticator attachment modality + if (\is_bool($crossPlatformAttachment)) { + $args->publicKey->authenticatorSelection->authenticatorAttachment = $crossPlatformAttachment ? 'cross-platform' : 'platform'; + } + + // user + $args->publicKey->user = new \stdClass(); + $args->publicKey->user->id = new ByteBuffer($userId); // binary + $args->publicKey->user->name = $userName; + $args->publicKey->user->displayName = $userDisplayName; + + // supported algorithms + $args->publicKey->pubKeyCredParams = array(); + $tmp = new \stdClass(); + $tmp->type = 'public-key'; + $tmp->alg = -7; // ES256 + $args->publicKey->pubKeyCredParams[] = $tmp; + unset ($tmp); + + $tmp = new \stdClass(); + $tmp->type = 'public-key'; + $tmp->alg = -257; // RS256 + $args->publicKey->pubKeyCredParams[] = $tmp; + unset ($tmp); + + // if there are root certificates added, we need direct attestation to validate + // against the root certificate. If there are no root-certificates added, + // anonymization ca are also accepted, because we can't validate the root anyway. + $attestation = 'indirect'; + if (\is_array($this->_caFiles)) { + $attestation = 'direct'; + } + + $args->publicKey->attestation = \count($this->_formats) === 1 && \in_array('none', $this->_formats) ? 'none' : $attestation; + $args->publicKey->extensions = new \stdClass(); + $args->publicKey->extensions->exts = true; + $args->publicKey->timeout = $timeout * 1000; // microseconds + $args->publicKey->challenge = $this->_createChallenge(); // binary + + //prevent re-registration by specifying existing credentials + $args->publicKey->excludeCredentials = array(); + + if (is_array($excludeCredentialIds)) { + foreach ($excludeCredentialIds as $id) { + $tmp = new \stdClass(); + $tmp->id = $id instanceof ByteBuffer ? $id : new ByteBuffer($id); // binary + $tmp->type = 'public-key'; + $tmp->transports = array('usb', 'nfc', 'ble', 'hybrid', 'internal'); + $args->publicKey->excludeCredentials[] = $tmp; + unset ($tmp); + } + } + + return $args; + } + + /** + * generates the object for key validation + * Provide this data to navigator.credentials.get + * @param array $credentialIds binary + * @param int $timeout timeout in seconds + * @param bool $allowUsb allow removable USB + * @param bool $allowNfc allow Near Field Communication (NFC) + * @param bool $allowBle allow Bluetooth + * @param bool $allowHybrid allow a combination of (often separate) data-transport and proximity mechanisms. + * @param bool $allowInternal allow client device-specific transport. These authenticators are not removable from the client device. + * @param bool|string $requireUserVerification indicates that you require user verification and will fail the operation + * if the response does not have the UV flag set. + * Valid values: + * true = required + * false = preferred + * string 'required' 'preferred' 'discouraged' + * @return \stdClass + */ + public function getGetArgs($credentialIds=array(), $timeout=20, $allowUsb=true, $allowNfc=true, $allowBle=true, $allowHybrid=true, $allowInternal=true, $requireUserVerification=false) { + + // validate User Verification Requirement + if (\is_bool($requireUserVerification)) { + $requireUserVerification = $requireUserVerification ? 'required' : 'preferred'; + } else if (\is_string($requireUserVerification) && \in_array(\strtolower($requireUserVerification), ['required', 'preferred', 'discouraged'])) { + $requireUserVerification = \strtolower($requireUserVerification); + } else { + $requireUserVerification = 'preferred'; + } + + $args = new \stdClass(); + $args->publicKey = new \stdClass(); + $args->publicKey->timeout = $timeout * 1000; // microseconds + $args->publicKey->challenge = $this->_createChallenge(); // binary + $args->publicKey->userVerification = $requireUserVerification; + $args->publicKey->rpId = $this->_rpId; + + if (\is_array($credentialIds) && \count($credentialIds) > 0) { + $args->publicKey->allowCredentials = array(); + + foreach ($credentialIds as $id) { + $tmp = new \stdClass(); + $tmp->id = $id instanceof ByteBuffer ? $id : new ByteBuffer($id); // binary + $tmp->transports = array(); + + if ($allowUsb) { + $tmp->transports[] = 'usb'; + } + if ($allowNfc) { + $tmp->transports[] = 'nfc'; + } + if ($allowBle) { + $tmp->transports[] = 'ble'; + } + if ($allowHybrid) { + $tmp->transports[] = 'hybrid'; + } + if ($allowInternal) { + $tmp->transports[] = 'internal'; + } + + $tmp->type = 'public-key'; + $args->publicKey->allowCredentials[] = $tmp; + unset ($tmp); + } + } + + return $args; + } + + /** + * returns the new signature counter value. + * returns null if there is no counter + * @return ?int + */ + public function getSignatureCounter() { + return \is_int($this->_signatureCounter) ? $this->_signatureCounter : null; + } + + /** + * process a create request and returns data to save for future logins + * @param string $clientDataJSON binary from browser + * @param string $attestationObject binary from browser + * @param string|ByteBuffer $challenge binary used challange + * @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin) + * @param bool $requireUserPresent false, if the device must NOT check user presence (e.g. by pressing a button) + * @param bool $failIfRootMismatch false, if there should be no error thrown if root certificate doesn't match + * @param bool $requireCtsProfileMatch false, if you don't want to check if the device is approved as a Google-certified Android device. + * @return \stdClass + * @throws WebAuthnException + */ + public function processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true, $requireCtsProfileMatch=true) { + $clientDataHash = \hash('sha256', $clientDataJSON, true); + $clientData = \json_decode($clientDataJSON); + $challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge); + + // security: https://www.w3.org/TR/webauthn/#registering-a-new-credential + + // 2. Let C, the client data claimed as collected during the credential creation, + // be the result of running an implementation-specific JSON parser on JSONtext. + if (!\is_object($clientData)) { + throw new WebAuthnException('invalid client data', WebAuthnException::INVALID_DATA); + } + + // 3. Verify that the value of C.type is webauthn.create. + if (!\property_exists($clientData, 'type') || $clientData->type !== 'webauthn.create') { + throw new WebAuthnException('invalid type', WebAuthnException::INVALID_TYPE); + } + + // 4. Verify that the value of C.challenge matches the challenge that was sent to the authenticator in the create() call. + if (!\property_exists($clientData, 'challenge') || ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()) { + throw new WebAuthnException('invalid challenge', WebAuthnException::INVALID_CHALLENGE); + } + + // 5. Verify that the value of C.origin matches the Relying Party's origin. + if (!\property_exists($clientData, 'origin') || !$this->_checkOrigin($clientData->origin)) { + throw new WebAuthnException('invalid origin', WebAuthnException::INVALID_ORIGIN); + } + + // Attestation + $attestationObject = new Attestation\AttestationObject($attestationObject, $this->_formats); + + // 9. Verify that the RP ID hash in authData is indeed the SHA-256 hash of the RP ID expected by the RP. + if (!$attestationObject->validateRpIdHash($this->_rpIdHash)) { + throw new WebAuthnException('invalid rpId hash', WebAuthnException::INVALID_RELYING_PARTY); + } + + // 14. Verify that attStmt is a correct attestation statement, conveying a valid attestation signature + if (!$attestationObject->validateAttestation($clientDataHash)) { + throw new WebAuthnException('invalid certificate signature', WebAuthnException::INVALID_SIGNATURE); + } + + // Android-SafetyNet: if required, check for Compatibility Testing Suite (CTS). + if ($requireCtsProfileMatch && $attestationObject->getAttestationFormat() instanceof Attestation\Format\AndroidSafetyNet) { + if (!$attestationObject->getAttestationFormat()->ctsProfileMatch()) { + throw new WebAuthnException('invalid ctsProfileMatch: device is not approved as a Google-certified Android device.', WebAuthnException::ANDROID_NOT_TRUSTED); + } + } + + // 15. If validation is successful, obtain a list of acceptable trust anchors + $rootValid = is_array($this->_caFiles) ? $attestationObject->validateRootCertificate($this->_caFiles) : null; + if ($failIfRootMismatch && is_array($this->_caFiles) && !$rootValid) { + throw new WebAuthnException('invalid root certificate', WebAuthnException::CERTIFICATE_NOT_TRUSTED); + } + + // 10. Verify that the User Present bit of the flags in authData is set. + $userPresent = $attestationObject->getAuthenticatorData()->getUserPresent(); + if ($requireUserPresent && !$userPresent) { + throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT); + } + + // 11. If user verification is required for this registration, verify that the User Verified bit of the flags in authData is set. + $userVerified = $attestationObject->getAuthenticatorData()->getUserVerified(); + if ($requireUserVerification && !$userVerified) { + throw new WebAuthnException('user not verified during authentication', WebAuthnException::USER_VERIFICATED); + } + + $signCount = $attestationObject->getAuthenticatorData()->getSignCount(); + if ($signCount > 0) { + $this->_signatureCounter = $signCount; + } + + // prepare data to store for future logins + $data = new \stdClass(); + $data->rpId = $this->_rpId; + $data->attestationFormat = $attestationObject->getAttestationFormatName(); + $data->credentialId = $attestationObject->getAuthenticatorData()->getCredentialId(); + $data->credentialPublicKey = $attestationObject->getAuthenticatorData()->getPublicKeyPem(); + $data->certificateChain = $attestationObject->getCertificateChain(); + $data->certificate = $attestationObject->getCertificatePem(); + $data->certificateIssuer = $attestationObject->getCertificateIssuer(); + $data->certificateSubject = $attestationObject->getCertificateSubject(); + $data->signatureCounter = $this->_signatureCounter; + $data->AAGUID = $attestationObject->getAuthenticatorData()->getAAGUID(); + $data->rootValid = $rootValid; + $data->userPresent = $userPresent; + $data->userVerified = $userVerified; + return $data; + } + + + /** + * process a get request + * @param string $clientDataJSON binary from browser + * @param string $authenticatorData binary from browser + * @param string $signature binary from browser + * @param string $credentialPublicKey string PEM-formated public key from used credentialId + * @param string|ByteBuffer $challenge binary from used challange + * @param int $prevSignatureCnt signature count value of the last login + * @param bool $requireUserVerification true, if the device must verify user (e.g. by biometric data or pin) + * @param bool $requireUserPresent true, if the device must check user presence (e.g. by pressing a button) + * @return boolean true if get is successful + * @throws WebAuthnException + */ + public function processGet($clientDataJSON, $authenticatorData, $signature, $credentialPublicKey, $challenge, $prevSignatureCnt=null, $requireUserVerification=false, $requireUserPresent=true) { + $authenticatorObj = new Attestation\AuthenticatorData($authenticatorData); + $clientDataHash = \hash('sha256', $clientDataJSON, true); + $clientData = \json_decode($clientDataJSON); + $challenge = $challenge instanceof ByteBuffer ? $challenge : new ByteBuffer($challenge); + + // https://www.w3.org/TR/webauthn/#verifying-assertion + + // 1. If the allowCredentials option was given when this authentication ceremony was initiated, + // verify that credential.id identifies one of the public key credentials that were listed in allowCredentials. + // -> TO BE VERIFIED BY IMPLEMENTATION + + // 2. If credential.response.userHandle is present, verify that the user identified + // by this value is the owner of the public key credential identified by credential.id. + // -> TO BE VERIFIED BY IMPLEMENTATION + + // 3. Using credential’s id attribute (or the corresponding rawId, if base64url encoding is + // inappropriate for your use case), look up the corresponding credential public key. + // -> TO BE LOOKED UP BY IMPLEMENTATION + + // 5. Let JSONtext be the result of running UTF-8 decode on the value of cData. + if (!\is_object($clientData)) { + throw new WebAuthnException('invalid client data', WebAuthnException::INVALID_DATA); + } + + // 7. Verify that the value of C.type is the string webauthn.get. + if (!\property_exists($clientData, 'type') || $clientData->type !== 'webauthn.get') { + throw new WebAuthnException('invalid type', WebAuthnException::INVALID_TYPE); + } + + // 8. Verify that the value of C.challenge matches the challenge that was sent to the + // authenticator in the PublicKeyCredentialRequestOptions passed to the get() call. + if (!\property_exists($clientData, 'challenge') || ByteBuffer::fromBase64Url($clientData->challenge)->getBinaryString() !== $challenge->getBinaryString()) { + throw new WebAuthnException('invalid challenge', WebAuthnException::INVALID_CHALLENGE); + } + + // 9. Verify that the value of C.origin matches the Relying Party's origin. + if (!\property_exists($clientData, 'origin') || !$this->_checkOrigin($clientData->origin)) { + throw new WebAuthnException('invalid origin', WebAuthnException::INVALID_ORIGIN); + } + + // 11. Verify that the rpIdHash in authData is the SHA-256 hash of the RP ID expected by the Relying Party. + if ($authenticatorObj->getRpIdHash() !== $this->_rpIdHash) { + throw new WebAuthnException('invalid rpId hash', WebAuthnException::INVALID_RELYING_PARTY); + } + + // 12. Verify that the User Present bit of the flags in authData is set + if ($requireUserPresent && !$authenticatorObj->getUserPresent()) { + throw new WebAuthnException('user not present during authentication', WebAuthnException::USER_PRESENT); + } + + // 13. If user verification is required for this assertion, verify that the User Verified bit of the flags in authData is set. + if ($requireUserVerification && !$authenticatorObj->getUserVerified()) { + throw new WebAuthnException('user not verificated during authentication', WebAuthnException::USER_VERIFICATED); + } + + // 14. Verify the values of the client extension outputs + // (extensions not implemented) + + // 16. Using the credential public key looked up in step 3, verify that sig is a valid signature + // over the binary concatenation of authData and hash. + $dataToVerify = ''; + $dataToVerify .= $authenticatorData; + $dataToVerify .= $clientDataHash; + + $publicKey = \openssl_pkey_get_public($credentialPublicKey); + if ($publicKey === false) { + throw new WebAuthnException('public key invalid', WebAuthnException::INVALID_PUBLIC_KEY); + } + + if (\openssl_verify($dataToVerify, $signature, $publicKey, OPENSSL_ALGO_SHA256) !== 1) { + throw new WebAuthnException('invalid signature', WebAuthnException::INVALID_SIGNATURE); + } + + $signatureCounter = $authenticatorObj->getSignCount(); + if ($signatureCounter !== 0) { + $this->_signatureCounter = $signatureCounter; + } + + // 17. If either of the signature counter value authData.signCount or + // previous signature count is nonzero, and if authData.signCount + // less than or equal to previous signature count, it's a signal + // that the authenticator may be cloned + if ($prevSignatureCnt !== null) { + if ($signatureCounter !== 0 || $prevSignatureCnt !== 0) { + if ($prevSignatureCnt >= $signatureCounter) { + throw new WebAuthnException('signature counter not valid', WebAuthnException::SIGNATURE_COUNTER); + } + } + } + + return true; + } + + /** + * Downloads root certificates from FIDO Alliance Metadata Service (MDS) to a specific folder + * https://fidoalliance.org/metadata/ + * @param string $certFolder Folder path to save the certificates in PEM format. + * @param bool $deleteCerts delete certificates in the target folder before adding the new ones. + * @return int number of cetificates + * @throws WebAuthnException + */ + public function queryFidoMetaDataService($certFolder, $deleteCerts=true) { + $url = 'https://mds.fidoalliance.org/'; + $raw = null; + if (\function_exists('curl_init')) { + $ch = \curl_init($url); + \curl_setopt($ch, CURLOPT_HEADER, false); + \curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + \curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + \curl_setopt($ch, CURLOPT_USERAGENT, 'github.com/lbuchs/WebAuthn - A simple PHP WebAuthn server library'); + $raw = \curl_exec($ch); + \curl_close($ch); + } else { + $raw = \file_get_contents($url); + } + + $certFolder = \rtrim(\realpath($certFolder), '\\/'); + if (!is_dir($certFolder)) { + throw new WebAuthnException('Invalid folder path for query FIDO Alliance Metadata Service'); + } + + if (!\is_string($raw)) { + throw new WebAuthnException('Unable to query FIDO Alliance Metadata Service'); + } + + $jwt = \explode('.', $raw); + if (\count($jwt) !== 3) { + throw new WebAuthnException('Invalid JWT from FIDO Alliance Metadata Service'); + } + + if ($deleteCerts) { + foreach (\scandir($certFolder) as $ca) { + if (\substr($ca, -4) === '.pem') { + if (\unlink($certFolder . DIRECTORY_SEPARATOR . $ca) === false) { + throw new WebAuthnException('Cannot delete certs in folder for FIDO Alliance Metadata Service'); + } + } + } + } + + list($header, $payload, $hash) = $jwt; + $payload = Binary\ByteBuffer::fromBase64Url($payload)->getJson(); + + $count = 0; + if (\is_object($payload) && \property_exists($payload, 'entries') && \is_array($payload->entries)) { + foreach ($payload->entries as $entry) { + if (\is_object($entry) && \property_exists($entry, 'metadataStatement') && \is_object($entry->metadataStatement)) { + $description = $entry->metadataStatement->description ?? null; + $attestationRootCertificates = $entry->metadataStatement->attestationRootCertificates ?? null; + + if ($description && $attestationRootCertificates) { + + // create filename + $certFilename = \preg_replace('/[^a-z0-9]/i', '_', $description); + $certFilename = \trim(\preg_replace('/\_{2,}/i', '_', $certFilename),'_') . '.pem'; + $certFilename = \strtolower($certFilename); + + // add certificate + $certContent = $description . "\n"; + $certContent .= \str_repeat('-', \mb_strlen($description)) . "\n"; + + foreach ($attestationRootCertificates as $attestationRootCertificate) { + $attestationRootCertificate = \str_replace(["\n", "\r", ' '], '', \trim($attestationRootCertificate)); + $count++; + $certContent .= "\n-----BEGIN CERTIFICATE-----\n"; + $certContent .= \chunk_split($attestationRootCertificate, 64, "\n"); + $certContent .= "-----END CERTIFICATE-----\n"; + } + + if (\file_put_contents($certFolder . DIRECTORY_SEPARATOR . $certFilename, $certContent) === false) { + throw new WebAuthnException('unable to save certificate from FIDO Alliance Metadata Service'); + } + } + } + } + } + + return $count; + } + + // ----------------------------------------------- + // PRIVATE + // ----------------------------------------------- + + /** + * checks if the origin matchs the RP ID + * @param string $origin + * @return boolean + * @throws WebAuthnException + */ + private function _checkOrigin($origin) { + // https://www.w3.org/TR/webauthn/#rp-id + + // The origin's scheme must be https + if ($this->_rpId !== 'localhost' && \parse_url($origin, PHP_URL_SCHEME) !== 'https') { + return false; + } + + // extract host from origin + $host = \parse_url($origin, PHP_URL_HOST); + $host = \trim($host, '.'); + + // The RP ID must be equal to the origin's effective domain, or a registrable + // domain suffix of the origin's effective domain. + return \preg_match('/' . \preg_quote($this->_rpId) . '$/i', $host) === 1; + } + + /** + * generates a new challange + * @param int $length + * @return string + * @throws WebAuthnException + */ + private function _createChallenge($length = 32) { + if (!$this->_challenge) { + $this->_challenge = ByteBuffer::randomBuffer($length); + } + return $this->_challenge; + } +} diff --git a/libs/webauthn/WebAuthnException.php b/libs/webauthn/WebAuthnException.php new file mode 100644 index 0000000..f27eeec --- /dev/null +++ b/libs/webauthn/WebAuthnException.php @@ -0,0 +1,28 @@ + Date: Sun, 3 Sep 2023 13:27:09 +0400 Subject: [PATCH 16/18] Updated the config --- .configuration/example_config.php | 77 +++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 10 deletions(-) diff --git a/.configuration/example_config.php b/.configuration/example_config.php index f7cfab5..ed92a77 100644 --- a/.configuration/example_config.php +++ b/.configuration/example_config.php @@ -55,7 +55,7 @@ function getScopes($expl_scopes, $infinite=0, $admin_required=false){ $spam_check = true; $spam_provider = "https://disposable.debounce.io/?email="; - + /* If using CAPTCHA (field $captcha_required is true), put the correct values in the field below. For more info - https://dash.cloudflare.com/?to=/:account/turnstile @@ -68,21 +68,71 @@ function getScopes($expl_scopes, $infinite=0, $admin_required=false){ */ $captcha_required = true; - $turnstile_public = ""; $turnstile_private = ""; $login_site = "https://example.com/login"; $status_page = "https://status.example.com/"; $support = "Either support link or EMail address."; + $support_email = "Either support link or EMail address."; + $platform_name = "DS Software ULS"; - $domain_name = "/"; /* / is default */ + $domain_name = "/"; # / is default $session_length = 32; $service_key = "Very_Long_Service_Key"; - $encryption_key = "Long_Key_For_AES_Encryption."; + $encryption_key = "Long_Key_For_AES_Encryption"; + + $enable_webauthn = true; + $user_verification_requirement = "required"; + /* + Types of UV Requirements: + - required : user must verify, otherwise fail + - preferred : user verification is preferred, but it won't fail + - discouraged : user verification should not be used + + Required might break some authenticators that + cannot verify users. + */ + + /* + Relying party ID is the address of your site, for example: + https://webauthn.example.com/auth relying party will be webauthn.example.com + */ + $relying_party_id = "example.com"; + + $attestation_formats = array( + "android-key" => [ + "name" => "Android Key", + "icon" => "fa-mobile" + ], + "android-safetynet" => [ + "name" => "Android SafetyNet", + "icon" => "fa-mobile" + ], + "apple" => [ + "name" => "Apple Attestation", + "icon" => "fa-mobile" + ], + "fido-u2f" => [ + "name" => "FIDO U2F", + "icon" => "fa-microchip" + ], + "none" => [ + "name" => "Passkey", + "icon" => "fa-key" + ], + "packed" => [ + "name" => "Hardware Key", + "icon" => "fa-microchip" + ], + "tpm" => [ + "name" => "TPM Attestation", + "icon" => "fa-desktop" + ] + ); $database = array( 'login' => 'database_login', @@ -92,24 +142,31 @@ function getScopes($expl_scopes, $infinite=0, $admin_required=false){ ); $email_info = array( - '$project_name' => "", + '$project_name' => $platform_name, '$main_link' => $login_site, '$login_site' => $login_site, - '$support_email' => "mailto:", - '$support_email_label' => "" + '$support_email' => "mailto:{$support_email}", + '$support_email_label' => $support_email ); + + /* + Do not use this feature unless you are experiencing severe issues with email delivery. This flag will disable all email verification. + + DO NOT USE THIS FEATURE ON A REAL SERVER! + */ + $disable_email = false; $email_settings = array( 'smtp' => 'your.smtp.provider', 'port' => '465', 'messageFrom' => 'Sender Name', 'login' => 'SMTP Login', - 'password' => 'SMTP Password' + 'password' => 'SMTP Password', + 'email_debug' => false // Use it when u get EMAIL_DELIVERY_FAULT error. ); $enable_creation = true; $int_url = $login_site . "/apps"; - $integrations_limit = 15; $allowed_admins = []; // [1 => true] ([USER_ID => true]) -?> \ No newline at end of file +?> From 7aff484a48574f1b31cf2474b211f9920d4dff3f Mon Sep 17 00:00:00 2001 From: DS Software Date: Sun, 3 Sep 2023 13:30:55 +0400 Subject: [PATCH 17/18] New database setup --- .configuration/database_setup.sql | 50 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/.configuration/database_setup.sql b/.configuration/database_setup.sql index dd4073e..bfc210c 100644 --- a/.configuration/database_setup.sql +++ b/.configuration/database_setup.sql @@ -12,24 +12,14 @@ CREATE TABLE `projects` ( `verified` int(11) NOT NULL, `enabled` int(11) NOT NULL DEFAULT 1, `banned` int(11) NOT NULL DEFAULT 0 -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; CREATE TABLE `requests` ( `request_id` int(10) UNSIGNED NOT NULL, `method` text NOT NULL, `request_ip` text NOT NULL, `request_time` int(11) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -CREATE TABLE `sessions` ( - `session_id` int(10) UNSIGNED NOT NULL, - `session` text NOT NULL, - `session_seed` text NOT NULL, - `session_ip` text NOT NULL, - `user_id` int(11) NOT NULL DEFAULT 0, - `claimed` int(11) NOT NULL DEFAULT 0, - `created` int(11) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; CREATE TABLE `users` ( `user_id` int(10) UNSIGNED NOT NULL, @@ -42,18 +32,26 @@ CREATE TABLE `users` ( `user_salt` text DEFAULT NULL, `password_hash` text NOT NULL, `ip_ver_code` text DEFAULT NULL, - `user_ip` text NOT NULL DEFAULT '', + `user_ip` text DEFAULT NULL, `api_key_seed` text DEFAULT NULL, - `SLID` text NOT NULL DEFAULT '', + `SLID` text DEFAULT NULL, `last_sid` text DEFAULT NULL, - `easylogin` int(11) NOT NULL DEFAULT 0, `email_check` int(11) NOT NULL DEFAULT 1, `2fa_active` int(11) NOT NULL DEFAULT 0, `2fa_secret` text DEFAULT NULL, `2fa_disable_code` text DEFAULT NULL, `is_banned` int(11) NOT NULL DEFAULT 0, - `ban_reason` text NOT NULL DEFAULT '' -) ENGINE=InnoDB DEFAULT CHARSET=utf8; + `ban_reason` text DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; + +CREATE TABLE `webauthn` ( + `keyID` int(10) UNSIGNED NOT NULL, + `user_id` text NOT NULL, + `owner_id` int(11) NOT NULL, + `final_key` mediumtext DEFAULT NULL, + `credential_id` text DEFAULT NULL, + `attest_type` text DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ALTER TABLE `projects` ADD PRIMARY KEY (`project_id`); @@ -61,21 +59,21 @@ ALTER TABLE `projects` ALTER TABLE `requests` ADD PRIMARY KEY (`request_id`); -ALTER TABLE `sessions` - ADD PRIMARY KEY (`session_id`); - ALTER TABLE `users` ADD PRIMARY KEY (`user_id`); +ALTER TABLE `webauthn` + ADD PRIMARY KEY (`keyID`); + ALTER TABLE `projects` - MODIFY `project_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; + MODIFY `project_id` int(1) UNSIGNED NOT NULL AUTO_INCREMENT; ALTER TABLE `requests` - MODIFY `request_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; - -ALTER TABLE `sessions` - MODIFY `session_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; + MODIFY `request_id` int(1) UNSIGNED NOT NULL AUTO_INCREMENT; ALTER TABLE `users` - MODIFY `user_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT; + MODIFY `user_id` int(1) UNSIGNED NOT NULL AUTO_INCREMENT; + +ALTER TABLE `webauthn` + MODIFY `keyID` int(1) UNSIGNED NOT NULL AUTO_INCREMENT; COMMIT; \ No newline at end of file From 896c21c602f07a02d64d2faa674a02e7345c28ed Mon Sep 17 00:00:00 2001 From: DS Software Date: Sun, 3 Sep 2023 14:04:04 +0400 Subject: [PATCH 18/18] some fixes added current account display as well as some bugfixes Closes #69 --- external_auth.php | 74 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/external_auth.php b/external_auth.php index 6cc3347..c73d9fe 100644 --- a/external_auth.php +++ b/external_auth.php @@ -25,8 +25,12 @@

Авторизация

+
+

+

Нажмите здесь, чтобы выйти.

+
-

+

запрашивает доступ к вашему аккаунту.

@@ -41,6 +45,7 @@ \ No newline at end of file