diff --git a/modules/2fa/modules.php b/modules/2fa/modules.php
index 67dca88b22..18354a13ce 100644
--- a/modules/2fa/modules.php
+++ b/modules/2fa/modules.php
@@ -109,6 +109,33 @@ public function process() {
}
}
+/**
+ * Verify 2fa code is paired with Authenticator app before enabling 2fa
+ * @subpackage 2fa/handler
+ */
+class Hm_Handler_2fa_setup_check extends Hm_Handler_Module {
+ public function process() {
+
+ list($secret, $simple) = get_2fa_key($this->config);
+ if (!$secret) {
+ Hm_Debug::add('2FA module set enabled, but no shared secret configured');
+ return;
+ }
+
+ $verified = false;
+ $len = $simple ? 15 : 64;
+
+ $username = $this->session->get('username', false);
+ $secret = create_secret($secret, $username, $len);
+
+ if (check_2fa_pin($this->request->post['2fa_code'], $secret)) {
+ $verified = true;
+ }
+
+ $this->out('ajax_2fa_verified', $verified);
+ }
+}
+
/**
* @subpackage 2fa/output
*/
@@ -120,40 +147,63 @@ protected function output() {
if (array_key_exists('2fa_enable', $settings)) {
$enabled = $settings['2fa_enable'];
}
- $res = '
'.
- ''.$this->trans('2 Factor Authentication').' |
';
+ $res = '
+ '.
+ ''.$this->trans('2 Factor Authentication').'
+ |
+
';
+
+ $res .= '
+
+ '.$this->trans('Enable 2 factor authentication').'
+ ';
- $res .= ' |
'.$this->trans('Enable 2 factor authentication').
- ' | get('2fa_svg');
+
if ($svg) {
- $qr_code = ' |
| ';
+ $qr_code = '';
if (!$enabled) {
- $qr_code .= ' '.
- $this->trans('Configure your authentication app using the barcode below BEFORE enabling 2 factor authentication.').' ';
+ $qr_code .= ''.$this->trans('Configure your authentication app using the barcode below BEFORE enabling 2 factor authentication.').' ';
}
else {
$qr_code .= ''.$this->trans('Update your settings with the code below').' ';
}
$qr_code .= $svg;
- $qr_code .= ' |
';
- $qr_code .= ' | '.$this->trans('If you can\'t use the QR code, you can enter the code below manually (no line breaks)').' |
';
- $qr_code .= ' | '.wordwrap($this->html_safe($this->get('2fa_secret', '')), 60, ' ', true).' |
';
+ $qr_code .= ''.$this->trans('If you can\'t use the QR code, you can enter the code below manually (no line breaks)').'
';
+ $qr_code .= wordwrap($this->html_safe($this->get('2fa_secret', '')), 60, '
', true);
}
else {
- $qr_code = ' | '.$this->trans('Unable to generate 2 factor authentication QR code').' |
';
+ $qr_code = ''.$this->trans('Unable to generate 2 factor authentication QR code').'
';
}
- $res .= $qr_code;
- $res .= ' | '.$this->trans('The following backup codes can be used to access your account if you lose your device').'
';
+ $res .= $qr_code . ' | ';
+
+ $res .= ''.$this->trans('The following backup codes can be used to access your account if you lose your device'). ' ';
+
foreach ($backup_codes as $val) {
$res .= ' '.$val.'';
}
- $res .= ' |
';
+ $res .= '
+
+
+
+ ';
return $res;
}
}
diff --git a/modules/2fa/setup.php b/modules/2fa/setup.php
index df0749de74..e3c9920b66 100644
--- a/modules/2fa/setup.php
+++ b/modules/2fa/setup.php
@@ -14,8 +14,20 @@
add_handler('settings', 'process_enable_2fa', true, '2fa', 'save_user_settings', 'before');
add_output('settings', 'enable_2fa_setting', true, '2fa', 'end_settings_form', 'before');
-return array( 'allowed_post' => array(
- '2fa_code' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
- '2fa_enable' => FILTER_VALIDATE_INT,
- '2fa_backup_codes' => array('filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_FORCE_ARRAY)
-));
+/* 2fa setup handler */
+setup_base_ajax_page('ajax_2fa_setup_check', 'core');
+add_handler('ajax_2fa_setup_check', '2fa_setup_check', true);
+
+return array(
+ 'allowed_pages' => array(
+ 'ajax_2fa_setup_check',
+ ),
+ 'allowed_post' => array(
+ '2fa_code' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
+ '2fa_enable' => FILTER_VALIDATE_INT,
+ '2fa_backup_codes' => array('filter' => FILTER_VALIDATE_INT, 'flags' => FILTER_FORCE_ARRAY)
+ ),
+ 'allowed_output' => array(
+ 'ajax_2fa_verified' => array(FILTER_VALIDATE_BOOLEAN, false),
+ ),
+);
diff --git a/modules/2fa/site.css b/modules/2fa/site.css
index 73601abafe..e7a856aa2b 100644
--- a/modules/2fa/site.css
+++ b/modules/2fa/site.css
@@ -1,4 +1,219 @@
-.tfa_error, .tfa_input { margin-left: 20px; }
-.tfa_error { color: red; margin-right: 20px; margin-bottom: 10px; }
-.tfa_setting { display: none; }
-.mobile .tfa_input { max-width: 340px; margin-bottom: 10px; }
+:root {
+ --tfa-form-valid-color: #198754;
+ --tfa-form-valid-border-color: #198754;
+ --tfa-form-invalid-color: #dc3545;
+ --tfa-form-invalid-border-color: #dc3545;
+ --tfa-border-radius: 0.375rem;
+ --tfa-border-color: #dee2e6;
+ --tfa-base-color: #666;
+ --tfa-secondary-color: rgba(33, 37, 41, 0.75);
+}
+
+.tfa_error,
+.tfa_input {
+ margin-left: 20px;
+}
+
+.tfa_error {
+ color: red;
+ margin-right: 20px;
+ margin-bottom: 10px;
+}
+
+.tfa_setting {
+ display: none;
+}
+
+.user_settings .tfa_setting td {
+ height: auto;
+}
+
+.mobile .tfa_input {
+ max-width: 340px;
+ margin-bottom: 10px;
+}
+
+.tfa_mt_1 {
+ margin-top: 0.7rem;
+}
+
+.tfa_mb_1 {
+ margin-bottom: 0.7rem;
+}
+
+.tfa_confirmation_fieldset {
+ color: var(--tfa-base-color);
+ border-radius: var(--tfa-border-radius);
+ border: 1px solid var(--tfa-border-color);
+ max-width: 365px;
+}
+
+.tfa_confirmation_wrapper .tfa_confirmation_hint {
+ font-style: italic;
+ font-size: 0.75em;
+ margin-top: 0.8rem;
+ color: var(--tfa-secondary-color);
+}
+.tfa_confirmation_wrapper .tfa_confirmation_hint .invalid {
+ color: var(--tfa-form-invalid-color);
+}
+.tfa_confirmation_wrapper .valid {
+ border-color: var(--tfa-form-valid-color);
+ padding-right: calc(1.5em + 0.75rem);
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+ background-repeat: no-repeat;
+ background-position: right calc(0.375em + 0.1875rem) center;
+ background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+}
+.tfa_confirmation_wrapper .invalid {
+ border-color: var(--tfa-form-invalid-color);
+}
+.tfa_confirmation_wrapper input::-webkit-outer-spin-button,
+.tfa_confirmation_wrapper input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+.tfa_confirmation_wrapper input[type=number] {
+ -moz-appearance: textfield;
+}
+.tfa_confirmation_wrapper input,
+.tfa_confirmation_wrapper button,
+.tfa_confirmation_wrapper a {
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+.tfa_confirmation_input_digits {
+ --total-digits: 6;
+ display: grid;
+ grid-template-columns: repeat(var(--total-digits), 1fr);
+ gap: 0.5rem;
+}
+.tfa_confirmation_input_digit {
+ display: block;
+ width: 2rem;
+ height: 2rem;
+ padding: 0.625rem;
+ border: 1px solid var(--tfa-border-color);
+ border-radius: var(--tfa-border-radius);
+ font-size: 1.5rem;
+ text-align: center;
+}
+.tfa_confirmation_input_digit:focus {
+ color: #212529;
+ background-color: #fff;
+ border-color: #86b7fe;
+ outline: 0;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+}
+.tfa_confirmation_input_button {
+ padding: 10px;
+ color: var(--tfa-base-color);
+ border-radius: var(--tfa-border-radius);
+ border: 1px solid var(--tfa-border-color);
+ background-color: #fff;
+ text-decoration: none;
+ margin-top: 0.8rem;
+ font-size: 0.9rem;
+}
+.tfa_confirmation_input_button:hover {
+ cursor: pointer;
+}
+.tfa_confirmation_input_button.invalid {
+ padding-right: calc(1.5em + 0.75rem);
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
+ background-repeat: no-repeat;
+ background-position: right calc(0.375em + 0.1875rem) center;
+ background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+ animation: shake 150ms 2 linear;
+ -moz-animation: shake 150ms 2 linear;
+ -webkit-animation: shake 150ms 2 linear;
+ -o-animation: shake 150ms 2 linear;
+}
+.tfa_confirmation_input_button.loading {
+ cursor: not-allowed;
+ opacity: 0.7;
+ padding-right: calc(1.75em + 0.75rem);
+ background-image: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiBub25lOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgd2lkdGg9IjIwMHB4IiBoZWlnaHQ9IjIwMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ieE1pZFlNaWQiPgo8ZyB0cmFuc2Zvcm09InJvdGF0ZSgwIDUwIDUwKSI+CiAgPHJlY3QgeD0iNDciIHk9IjI0IiByeD0iMyIgcnk9IjYiIHdpZHRoPSI2IiBoZWlnaHQ9IjEyIiBmaWxsPSIjYTNhM2EzIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9Im9wYWNpdHkiIHZhbHVlcz0iMTswIiBrZXlUaW1lcz0iMDsxIiBkdXI9IjFzIiBiZWdpbj0iLTAuOTE2NjY2NjY2NjY2NjY2NnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIj48L2FuaW1hdGU+CiAgPC9yZWN0Pgo8L2c+PGcgdHJhbnNmb3JtPSJyb3RhdGUoMzAgNTAgNTApIj4KICA8cmVjdCB4PSI0NyIgeT0iMjQiIHJ4PSIzIiByeT0iNiIgd2lkdGg9IjYiIGhlaWdodD0iMTIiIGZpbGw9IiNhM2EzYTMiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ib3BhY2l0eSIgdmFsdWVzPSIxOzAiIGtleVRpbWVzPSIwOzEiIGR1cj0iMXMiIGJlZ2luPSItMC44MzMzMzMzMzMzMzMzMzM0cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiPjwvYW5pbWF0ZT4KICA8L3JlY3Q+CjwvZz48ZyB0cmFuc2Zvcm09InJvdGF0ZSg2MCA1MCA1MCkiPgogIDxyZWN0IHg9IjQ3IiB5PSIyNCIgcng9IjMiIHJ5PSI2IiB3aWR0aD0iNiIgaGVpZ2h0PSIxMiIgZmlsbD0iI2EzYTNhMyI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJvcGFjaXR5IiB2YWx1ZXM9IjE7MCIga2V5VGltZXM9IjA7MSIgZHVyPSIxcyIgYmVnaW49Ii0wLjc1cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiPjwvYW5pbWF0ZT4KICA8L3JlY3Q+CjwvZz48ZyB0cmFuc2Zvcm09InJvdGF0ZSg5MCA1MCA1MCkiPgogIDxyZWN0IHg9IjQ3IiB5PSIyNCIgcng9IjMiIHJ5PSI2IiB3aWR0aD0iNiIgaGVpZ2h0PSIxMiIgZmlsbD0iI2EzYTNhMyI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJvcGFjaXR5IiB2YWx1ZXM9IjE7MCIga2V5VGltZXM9IjA7MSIgZHVyPSIxcyIgYmVnaW49Ii0wLjY2NjY2NjY2NjY2NjY2NjZzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSI+PC9hbmltYXRlPgogIDwvcmVjdD4KPC9nPjxnIHRyYW5zZm9ybT0icm90YXRlKDEyMCA1MCA1MCkiPgogIDxyZWN0IHg9IjQ3IiB5PSIyNCIgcng9IjMiIHJ5PSI2IiB3aWR0aD0iNiIgaGVpZ2h0PSIxMiIgZmlsbD0iI2EzYTNhMyI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJvcGFjaXR5IiB2YWx1ZXM9IjE7MCIga2V5VGltZXM9IjA7MSIgZHVyPSIxcyIgYmVnaW49Ii0wLjU4MzMzMzMzMzMzMzMzMzRzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSI+PC9hbmltYXRlPgogIDwvcmVjdD4KPC9nPjxnIHRyYW5zZm9ybT0icm90YXRlKDE1MCA1MCA1MCkiPgogIDxyZWN0IHg9IjQ3IiB5PSIyNCIgcng9IjMiIHJ5PSI2IiB3aWR0aD0iNiIgaGVpZ2h0PSIxMiIgZmlsbD0iI2EzYTNhMyI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJvcGFjaXR5IiB2YWx1ZXM9IjE7MCIga2V5VGltZXM9IjA7MSIgZHVyPSIxcyIgYmVnaW49Ii0wLjVzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSI+PC9hbmltYXRlPgogIDwvcmVjdD4KPC9nPjxnIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA1MCkiPgogIDxyZWN0IHg9IjQ3IiB5PSIyNCIgcng9IjMiIHJ5PSI2IiB3aWR0aD0iNiIgaGVpZ2h0PSIxMiIgZmlsbD0iI2EzYTNhMyI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJvcGFjaXR5IiB2YWx1ZXM9IjE7MCIga2V5VGltZXM9IjA7MSIgZHVyPSIxcyIgYmVnaW49Ii0wLjQxNjY2NjY2NjY2NjY2NjdzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSI+PC9hbmltYXRlPgogIDwvcmVjdD4KPC9nPjxnIHRyYW5zZm9ybT0icm90YXRlKDIxMCA1MCA1MCkiPgogIDxyZWN0IHg9IjQ3IiB5PSIyNCIgcng9IjMiIHJ5PSI2IiB3aWR0aD0iNiIgaGVpZ2h0PSIxMiIgZmlsbD0iI2EzYTNhMyI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJvcGFjaXR5IiB2YWx1ZXM9IjE7MCIga2V5VGltZXM9IjA7MSIgZHVyPSIxcyIgYmVnaW49Ii0wLjMzMzMzMzMzMzMzMzMzMzNzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSI+PC9hbmltYXRlPgogIDwvcmVjdD4KPC9nPjxnIHRyYW5zZm9ybT0icm90YXRlKDI0MCA1MCA1MCkiPgogIDxyZWN0IHg9IjQ3IiB5PSIyNCIgcng9IjMiIHJ5PSI2IiB3aWR0aD0iNiIgaGVpZ2h0PSIxMiIgZmlsbD0iI2EzYTNhMyI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJvcGFjaXR5IiB2YWx1ZXM9IjE7MCIga2V5VGltZXM9IjA7MSIgZHVyPSIxcyIgYmVnaW49Ii0wLjI1cyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiPjwvYW5pbWF0ZT4KICA8L3JlY3Q+CjwvZz48ZyB0cmFuc2Zvcm09InJvdGF0ZSgyNzAgNTAgNTApIj4KICA8cmVjdCB4PSI0NyIgeT0iMjQiIHJ4PSIzIiByeT0iNiIgd2lkdGg9IjYiIGhlaWdodD0iMTIiIGZpbGw9IiNhM2EzYTMiPgogICAgPGFuaW1hdGUgYXR0cmlidXRlTmFtZT0ib3BhY2l0eSIgdmFsdWVzPSIxOzAiIGtleVRpbWVzPSIwOzEiIGR1cj0iMXMiIGJlZ2luPSItMC4xNjY2NjY2NjY2NjY2NjY2NnMiIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIj48L2FuaW1hdGU+CiAgPC9yZWN0Pgo8L2c+PGcgdHJhbnNmb3JtPSJyb3RhdGUoMzAwIDUwIDUwKSI+CiAgPHJlY3QgeD0iNDciIHk9IjI0IiByeD0iMyIgcnk9IjYiIHdpZHRoPSI2IiBoZWlnaHQ9IjEyIiBmaWxsPSIjYTNhM2EzIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9Im9wYWNpdHkiIHZhbHVlcz0iMTswIiBrZXlUaW1lcz0iMDsxIiBkdXI9IjFzIiBiZWdpbj0iLTAuMDgzMzMzMzMzMzMzMzMzMzNzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSI+PC9hbmltYXRlPgogIDwvcmVjdD4KPC9nPjxnIHRyYW5zZm9ybT0icm90YXRlKDMzMCA1MCA1MCkiPgogIDxyZWN0IHg9IjQ3IiB5PSIyNCIgcng9IjMiIHJ5PSI2IiB3aWR0aD0iNiIgaGVpZ2h0PSIxMiIgZmlsbD0iI2EzYTNhMyI+CiAgICA8YW5pbWF0ZSBhdHRyaWJ1dGVOYW1lPSJvcGFjaXR5IiB2YWx1ZXM9IjE7MCIga2V5VGltZXM9IjA7MSIgZHVyPSIxcyIgYmVnaW49IjBzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSI+PC9hbmltYXRlPgogIDwvcmVjdD4KPC9nPgo8IS0tIFtsZGlvXSBnZW5lcmF0ZWQgYnkgaHR0cHM6Ly9sb2FkaW5nLmlvLyAtLT48L3N2Zz4=");
+ background-repeat: no-repeat;
+ background-position: right calc(0.375em + 0.1875rem) center;
+ background-size: calc(1.5em + 0.375rem) calc(1.5em + 0.375rem);
+}
+
+@media (max-width: 767px) {
+ .hidden-xs {
+ display: none !important;
+ }
+
+ .tfa_confirmation_fieldset {
+ max-width: 100vh;
+ }
+ .tfa_confirmation_fieldset .tfa_confirmation_input_digit {
+ width: 1.5rem;
+ height: 1.5rem;
+ }
+
+ .user_settings .tfa_setting td {
+ display: block;
+ }
+}
+/**
+** Shake Annimation Starts Here.
+**/
+@keyframes shake {
+ 0% {
+ transform: translate(3px, 0);
+ }
+ 50% {
+ transform: translate(-3px, 0);
+ }
+ 100% {
+ transform: translate(0, 0);
+ }
+}
+@-moz-keyframes shake {
+ 0% {
+ -moz-transform: translate(3px, 0);
+ }
+ 50% {
+ -moz-transform: translate(-3px, 0);
+ }
+ 100% {
+ -moz-transform: translate(0, 0);
+ }
+}
+@-webkit-keyframes shake {
+ 0% {
+ -webkit-transform: translate(3px, 0);
+ }
+ 50% {
+ -webkit-transform: translate(-3px, 0);
+ }
+ 100% {
+ -webkit-transform: translate(0, 0);
+ }
+}
+@-ms-keyframes shake {
+ 0% {
+ -ms-transform: translate(3px, 0);
+ }
+ 50% {
+ -ms-transform: translate(-3px, 0);
+ }
+ 100% {
+ -ms-transform: translate(0, 0);
+ }
+}
+@-o-keyframes shake {
+ 0% {
+ -o-transform: translate(3px, 0);
+ }
+ 50% {
+ -o-transform: translate(-3px, 0);
+ }
+ 100% {
+ -o-transform: translate(0, 0);
+ }
+}
+/**
+ ** Shake Annimation Ends Here.
+ **/
\ No newline at end of file
diff --git a/modules/2fa/site.js b/modules/2fa/site.js
new file mode 100644
index 0000000000..ec8b68ec70
--- /dev/null
+++ b/modules/2fa/site.js
@@ -0,0 +1,175 @@
+$(function () {
+ function tFaToast(message, timer = 3000) {
+ Hm_Notices.show([message]);
+ const tm = setTimeout(function () {
+ Hm_Notices.hide(true);
+ clearTimeout(tm);
+ }, timer);
+ }
+
+ function focusElement(elem) {
+ elem.focus();
+ elem.select();
+ }
+
+ function validateInput(input) {
+ if (isNaN(Number(input.value)) || input.value === "") {
+ input.classList.add("invalid");
+ return false;
+ }
+ input.classList.remove("invalid");
+ return input.value.length > 1 ? input.value[0] : input.value;
+ }
+
+ function focusNextInput(currentInput) {
+ if (currentInput.nextElementSibling) {
+ focusElement(currentInput.nextElementSibling);
+ }
+ }
+
+ function focusPrevInput(currentInput) {
+ if (currentInput.previousElementSibling) {
+ focusElement(currentInput.previousElementSibling);
+ }
+ }
+
+ function handleArrowKeys(e) {
+ if (e.key == "ArrowRight") {
+ e.preventDefault();
+ focusNextInput(e.target);
+ }
+ if (e.key == "ArrowLeft") {
+ e.preventDefault();
+ focusPrevInput(e.target);
+ }
+ }
+
+ function handleBackspace(e) {
+ if (e.key == "Backspace") {
+ e.target.value = "";
+ e.target.classList.remove("invalid");
+ focusPrevInput(e.target);
+ }
+ }
+
+ function getInputCode() {
+ let inputCode = "";
+ for (let input of $(".tfa_confirmation_input_digit")) {
+ input.classList.remove("invalid");
+ if (validateInput(input) === false) {
+ return null;
+ }
+ inputCode += input.value;
+ input.blur();
+ }
+ return inputCode;
+ }
+
+ function tFaInit() {
+ let verified = false;
+
+ const formInput = $('input[name="2fa_enable"]');
+ const tfaEnabled = formInput.is(":checked");
+ const confirmationBtn = $("#tfaConfirmationBtn");
+ const formContainer = $(".tfa_confirmation_form");
+
+ if (!tfaEnabled) {
+ formInput.on("change", function () {
+ const checked = $(this).is(":checked");
+
+ if (checked && !verified) {
+ $(this).prop("checked", false);
+ tFaToast("ERRYou need to verify your 2 factor authentication code before processing");
+ return;
+ }
+ });
+ }
+
+ formContainer.on("input", function (e) {
+ const value = validateInput(e.target);
+ if (value !== false) {
+ e.target.value = value;
+ focusNextInput(e.target);
+ } else {
+ e.target.value = "";
+ }
+ });
+
+ formContainer.on("click", function (e) {
+ if (e.target.tagName == "INPUT") {
+ focusElement(e.target);
+ }
+ });
+
+ formContainer.on("keydown", ".tfa_confirmation_input_digit", function (e) {
+ handleArrowKeys(e);
+ handleBackspace(e);
+ });
+
+ formContainer.on("paste", function (e) {
+ const pastedData = e.originalEvent.clipboardData.getData("text");
+
+ if (pastedData === "") {
+ return;
+ }
+ const payloadData = pastedData.split("");
+
+ const inputs = $(".tfa_confirmation_input_digit");
+
+ inputs.each(function (i, input) {
+ $(input).val(payloadData[i]).focus();
+ });
+
+ const tm = setTimeout(() => {
+ for (let input of inputs) {
+ if (validateInput(input) === false) {
+ focusElement(input);
+ break;
+ }
+ }
+ clearTimeout(tm);
+ }, 0);
+ });
+
+ confirmationBtn.on("click", function (e) {
+ e.preventDefault();
+ $(this).removeClass("invalid").removeClass("shake");
+ $(".tfa_confirmation_input_digit").removeClass("invalid");
+ var code = getInputCode();
+
+ if (!code) {
+ tFaToast("ERRYou need to enter the verification code");
+ var tm = setTimeout(function () {
+ Hm_Notices.hide(true);
+ clearTimeout(tm);
+ }, 2000);
+ return;
+ }
+
+ $(this).text("Processing").addClass("loading");
+ Hm_Ajax.request(
+ [
+ { name: "hm_ajax_hook", value: "ajax_2fa_setup_check" },
+ { name: "2fa_code", value: code },
+ ],
+ function (response) {
+ if (response && response.ajax_2fa_verified) {
+ verified = true;
+ formInput.prop("checked", true);
+ confirmationBtn.addClass("valid");
+ tFaToast("2 factor authentication enabled");
+ } else {
+ verified = false;
+ formInput.prop("checked", false);
+ $(".tfa_confirmation_input_digit").addClass("invalid");
+ confirmationBtn.addClass("invalid").addClass("shake");
+ tFaToast("ERR2 factor authentication code does not match");
+ }
+ confirmationBtn.text("Verify code").removeClass("loading");
+ }
+ );
+ });
+ }
+
+ tFaInit();
+});