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 .= '
+
+ Enter the confirmation code +
+
+
+ + + + + + +
+ +
+
'.$this->trans('Enter the 6 digit code from your Authenticator application').'
+
+
+
+ + '; 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(); +});