1$(document).ready(function () { 2 const loginForm = $('.form-simple'); 3 const loginButton = $('#loginSubmitButton'); 4 5 // FontAwesome icons 6 const faExclamation = '<i class="fa fa-exclamation-circle" aria-hidden="true"></i>'; 7 const faCheck = '<i class="fa fa-check" aria-hidden="true"></i>'; 8 9 let enabled2fa = false; 10 11 let userEmailAddress; 12 let userMobileNumber; 13 14 let userPassword; 15 16 let number_or_email; 17 18 let otpChannel; // 'EMAIL' OR 'MOBILE' 19 20 let disableSendOtp = false; 21 let disableVerifyOtp = false; 22 let is_plivo_stop_received = false; 23 24 25 // Prevent form submit by default 26 loginForm.submit(function (event) { 27 event.preventDefault(); 28 }) 29 30 loginButton.on('click', function () { 31 // Don't proceed if email address or password field is empty 32 if ($('#user-email').val().trim() === '' || $('#user-password').val().trim() === '') { 33 loginForm.unbind('submit').submit(); 34 return; 35 } 36 37 validatePasswordAndCheck2faStatus(); 38 }) 39 40 function validatePasswordAndCheck2faStatus() { 41 userEmailAddress = $('#user-email').val(); 42 userPassword = $('#user-password').val(); 43 44 /* 45 // Send password as an encoded string 46 if (userPassword != '') { 47 try { 48 userPassword = window.btoa(userPassword); 49 } catch (error) { 50 userPassword = ''; 51 } 52 } 53 */ 54 55 $.ajax({ 56 type: 'GET', 57 url: base_url + 'validate_password_2fa/', 58 dataType: 'json', 59 data: { 60 'user-email': userEmailAddress, 61 'user-password': userPassword 62 }, 63 statusCode: { 64 // Incorrect password entered 65 422: function () { 66 loginForm.unbind('submit').submit(); 67 } 68 }, 69 success: function (resp) { 70 is_plivo_stop_received = resp['context']['is_plivo_stop_received'] 71 final_url = resp['context']['final_url'] 72 user_id = resp['context']['user_id'] 73 74 via_plivo_webhook = resp['context']['via_plivo_webhook'] 75 via_xten_av_link = resp['context']['via_xten_av_link'] 76 77 enabled2fa = resp['context']['enabled_2fa']; 78 const device_remembered = resp['context']['device_remembered'] 79 const incorrectAttemptsLock = resp['context']['incorrect_attempts_lock'] 80 const otpRequestsLock = resp['context']['otp_requests_lock'] 81 const resetRequestsLockTime = new Date(resp['context']['otp_requests_lock_reset_time']); 82 const twoFaRender = resp['context']['two_fa_render']; 83 const ssoEnable= resp['context']['sso_enabled']; 84 85 userMobileNumber = resp['context']['number']; 86 87 // Continue with login if user has not enabled 2FA or has remembered this device 88 89 if (!enabled2fa || (enabled2fa && device_remembered) || ssoEnable) { 90 loginForm.unbind('submit').submit(); 91 return; 92 } 93 // else{ 94 // loginForm.unbind('submit').submit(); 95 // window.location.href = base_url + 'enable_2fa_login_form'; 96 // } 97 98 99 // Prepend Two Factor form to login section and hide login form 100 // $('.login-section').prepend(twoFaRender); 101 102 // Show appropriate message to user and disable access to account 103 // if user is locked out of his account 104 if (enabled2fa && incorrectAttemptsLock) { 105 tooManyIncorrectAttemptsLock(); 106 } else if (enabled2fa && otpRequestsLock) { 107 disableAdditionalOtpRequests(); 108 createRequestLockTimer(resetRequestsLockTime); 109 } else { 110 // Only add event listeners to buttons if user is not locked out of accessing his account 111 addTwoFactorEventListeners(); 112 } 113 114 $('.login-section .login-section-main').addClass('d-none'); 115 $('#twoFactorForm2fa').removeClass('d-none'); 116 117 $('.right-sec .content-box .head').text('Two-Factor Authentication'); 118 $('.right-sec .content-box .text').text('You have enabled-Two-Factor Authentication for your account. Follow the instructions to log in to the system.'); 119 $('.right-sec .content-box .btn-wrapper').addClass('d-none'); 120 } 121 }); 122 } 123 124 function sendEmailOtp() {
125 // Do nothing if send OTP action is disabled 126 if (disableSendOtp) { 127 return; 128 } 129 130 otpChannel = 'EMAIL'; 131 132 // Initiate send OTP procedure if all checks above are passed 133 sendOtp(); 134 } 135 136 function sendMobileOtp() { 137 // Do nothing if send OTP action is disabled 138 if (disableSendOtp) { 139 return; 140 } 141 142 otpChannel = 'MOBILE'; 143 144 // Initiate send OTP procedure if all checks above are passed 145 sendOtp(); 146 } 147 148 function resubscribeNotifications() { 149 $.ajax({ 150 type: 'POST', 151 url: base_url + 'user-opt-in-status/', 152 contentType: 'application/json', 153 data: JSON.stringify({ 154 user_id: user_id, // encoded uid 155 is_subscribed: true 156 }), 157 success: function(response) { 158 if (response.success) { 159 swal("Success!", "OTP sent successfully!", "success"); 160 swal.close(); 161 window.location.reload(); 162 } else { 163 swal("Error!", "Something went wrong. Please try again.", "error"); 164 } 165 }, 166 error: function(xhr) { 167 console.log('Error:', xhr.status, xhr.responseText); 168 swal("Error!", "Something went wrong. Please try again.", "error"); 169 } 170 }); 171 } 172 173 function sendOtp() { 174 $('.otp-status').html(''); 175 176 // Clear error message span 177 $('.otp-verify-info small').html(''); 178 179 // Clear input field 180 $('.digit-group input').each(function () { 181 $(this).val(''); 182 }) 183 184 otpChannel === 'EMAIL' ? number_or_email = userEmailAddress : number_or_email = userMobileNumber; 185 186 if (otpChannel === 'MOBILE') { 187 188 let manageXtenLink = ""; 189 let managePlivoLink = ""; 190 let isXtenCase = false; 191 192 /* ------------------------- 193 CASE 1: PLIVO STOP 194 --------------------------*/ 195 if (via_plivo_webhook) { 196 managePlivoLink = ` 197 Youâve opted out of SMS messages on this number, so we canât send OTP right now. 198 To continue, reply <strong>START</strong> to 199 <span style="white-space:nowrap; font-weight:600;">+1 (747-399-7227)</span> 200 from your phone. Once reactivated, you can verify your number. 201 `; 202 } 203 204 /* ------------------------- 205 CASE 2: XTEN STOP 206 --------------------------*/ 207 if (!via_plivo_webhook && via_xten_av_link) { 208 isXtenCase = true; 209 210 manageXtenLink = ` 211 <div style="margin-top:12px; text-align:center;"> 212 <div style="text-align:left; margin-bottom:10px;"> 213 Youâve opted out of SMS messages on this number, so we canât send OTP right now. 214 To continue, Click <strong>Enable SMS</strong> to start receiving OTPs and important account updates again. 215 </div> 216 217 <button id="resubscribeBtn" 218 style=" 219 background-color: #2863a7; 220 color: white; 221 border: none; 222 padding: 10px 16px; 223 border-radius: 6px; 224 cursor: pointer; 225 "> 226 Enable SMS 227 </button> 228 </div> 229 `; 230 } 231 232 233 /* ------------------------- 234 SHOW MODAL 235 --------------------------*/ 236 if (String(is_plivo_stop_received).toLowerCase() === "true") { 237 238 swal({ 239 title: "SMS Notifications Paused", 240 content: { 241 element: "div", 242 attributes: { 243 innerHTML: ` 244 <div style="text-align:left;"> 245 ${managePlivoLink} 246 ${manageXtenLink} 247 </div> 248 ` 249 } 250 }, 251 icon: "warning", 252 253 button: isXtenCase ? null : "OK" 254 }).then(() => { 255 if (!isXtenCase) { 256 location.reload(); 257 } 258 }); 259 260 261 if (isXtenCase) { 262 setTimeout(function() { 263 var btn = document.getElementById('resubscribeBtn'); 264 if (btn) { 265 btn.addEventListener('click', function() { 266 btn.innerText = "Enabling..."; 267 btn.disabled = true; 268 269 resubscribeNotifications(); 270 }); 271 } 272 }, 300); 273 } 274 275 return; 276 } 277 } 278 279 // Disable multiple OTP sends if user clicks multiple times simultaneously 280 $('.otp-btn-group button').addClass('no-pointer'); 281 282 $.ajax({ 283 type: 'GET', 284 url: base_url + 'send_otp_2fa/', 285 dataType: 'json', 286 data: { 287 'user-email': userEmailAddress, 288 'user-password': userPassword, 289 'channel': otpChannel, 290 'process': 'LOGIN', 291 'number_or_email': number_or_email 292 }, 293 statusCode: { 294 403: function (resp) { 295 disableAdditionalOtpRequests(); 296 297 const minutes = 60; 298 const currentTime = new Date(); 299 const resetTime = new Date(currentTime.getTime() + minutes * 60 * 1000); 300 301 createRequestLockTimer(resetTime) 302 }, 303 404: function () { 304 showOtpMessage(action = 'sendOtp', messageType = 'otpSendFailure'); 305 }, 306 422: function () { 307 // Invalid Email/ Phone Number 308 alert('Invalid email/ phone..error message yet to handle'); 309 }, 310 500: function () { 311 showOtpMessage(action = 'sendOtp', messageType = 'otpSendFailure'); 312 } 313 }, 314 success: function () { 315 showOtpMessage(action = 'sendOtp', messageType = 'otpSendSuccess'); 316 317 // Create resend timer 318 createResendTimer(); 319 } 320 }).always(function() { 321 $('.otp-btn-group button').removeClass('no-pointer'); 322 }) 323 } 324 325 function verifyOtp() {
326 // Do nothing if verify OTP action is disabled 327 if (disableVerifyOtp) { 328 return; 329 } 330 331 $('.otp-verify-info small').html(''); 332 333 let otpInput = ''; 334 335 $('.digit-group input').each(function () { 336 otpInput += $(this).val(); 337 }) 338 339 // Disable user to send multiple verify requests simultaneously 340 $('#verifyOtp2fa').addClass('no-pointer'); 341 342 $.ajax({ 343 type: 'GET', 344 url: base_url + 'verify_otp_2fa', 345 dataType: 'json', 346 data: { 347 'user-email': userEmailAddress, 348 'user-password': userPassword, 349 'channel': otpChannel, 350 'process': 'LOGIN', 351 'code': otpInput, 352 'number_or_email': otpChannel === 'EMAIL' ? userEmailAddress : userMobileNumber 353 }, 354 statusCode: { 355 403: function () { 356 // Too many incorrect OTP attempts 357 tooManyIncorrectAttemptsLock(); 358 }, 359 404: function () { 360 showOtpMessage(action = 'verifyOtp', messageType = 'otpAuthenticationFailed'); 361 }, 362 409: function () { 363 showOtpMessage(action = 'verifyOtp', messageType = 'otpExpired'); 364 }, 365 422: function () { 366 showOtpMessage(action = 'verifyOtp', messageType = 'otpIncorrect'); 367 }, 368 500: function () { 369 showOtpMessage(action = 'verifyOtp', messageType = 'otpAuthenticationFailed'); 370 } 371 }, 372 success: function () { 373 showOtpMessage(action = 'verifyOtp', messageType = 'otpVerified'); 374 375 if ($('#rememberDevice').prop('checked')) { 376 loginForm[0].rememberDevice2fa.value = true; 377 378 const deviceDescription = platform.toString(); 379 loginForm[0].deviceDescription.value = deviceDescription; 380 381 } 382 383 // Submit form and login the user if OTP is successfully verified 384 loginForm.unbind('submit').submit(); 385 }, 386 error: function() { 387 $('#verifyOtp2fa').removeClass('no-pointer'); 388 } 389 }); 390 } 391 392 function showOtpMessage(action = null, messageType = null) { 393 /* 394 action: On what action did the error occur: Valid values are: 'sendOtp', 'verifyOtp' 395 messageType: What kind of message to show. Valid values are: 396 action === 'sendOtp': 397 Error Messages: otpSendFailure 398 Success Messages: otpSendSuccess 399 action === 'verifyOtp': 400 Error Messages: otpIncorrect, otpExpired, otpAuthenticationFailed 401 Success Messages: otpVerified 402 */ 403 404 const otpStatusSpan = $('.otp-status'); 405 const verifyOtpInfoSpan = $('.otp-verify-info small'); 406 407 const messageObject = { 408 'sendOtp': { 409 // Error Messages 410 'otpSendFailure': `${faExclamation} Unable to send OTP. Please try again after some time`, 411 412 // Success Messages 413 'otpSendSuccess': `${faCheck} OTP sent successfully` 414 }, 415 'verifyOtp': { 416 // Error Messages 417 'otpIncorrect': `${faExclamation} Incorrect OTP entered`, 418 'otpExpired': `${faExclamation} Your OTP has expired. Please resend OTP and try again`, 419 'otpAuthenticationFailed': `${faExclamation} Unable to verify OTP. Please resend OTP and try again`, 420 421 // Success Messages 422 'otpVerified': `${faCheck} OTP verified! Signing you in` 423 } 424 } 425 426 if (action === 'sendOtp') { 427 if (messageType === 'otpSendSuccess') { 428 otpStatusSpan.removeClass('red').addClass('green'); 429 } else { 430 otpStatusSpan.removeClass('green').addClass('red'); 431 } 432 433 otpStatusSpan.html(messageObject[action][messageType]); 434 435 } else if (action === 'verifyOtp') { 436 if (messageType === 'otpVerified') {
437 verifyOtpInfoSpan.removeClass('red').addClass('green'); 438 } else { 439 verifyOtpInfoSpan.removeClass('green').addClass('red'); 440 } 441 442 verifyOtpInfoSpan.html(messageObject[action][messageType]); 443 } 444 } 445 446 function createResendTimer() { 447 const resendTimerSpan = $('.otp-status'); 448 449 let seconds = 57; 450 451 // Disable user to send OTP until timer expires 452 disableSendOtp = true; 453 $('#sendEmailOtp').prop('disabled', true); 454 $('#sendMobileOtp').prop('disabled', true); 455 456 setTimeout(function () { 457 let timer = setInterval(function () { 458 resendTimerSpan.removeClass('green'); 459 resendTimerSpan.text(`Resend in ${seconds}s`); 460 461 // Remove timer and enable send OTP buttons when timer expires 462 if ((seconds <= 0) && disableVerifyOtp) { 463 clearInterval(timer); 464 resendTimerSpan.text(''); 465 } else if ((seconds <= 0 && !disableVerifyOtp)) { 466 clearInterval(timer); 467 468 disableSendOtp = false; 469 470 $('#sendEmailOtp').prop('disabled', false); 471 $('#sendMobileOtp').prop('disabled', false); 472 473 resendTimerSpan.text(''); 474 } 475 476 seconds --; 477 478 }, 1000) // Update timer every second 479 }, 3000); // Show resend timer 3 secs after being initialized 480 } 481 482 function disableAdditionalOtpRequests() { 483 /* 484 Disables user to send additional OTP requests if he/ she has exceeded the number of 485 OTP requests allowed within a certain time period 486 */ 487 488 // Clear other message spans 489 $('.otp-verify-info span').html(''); 490 $('.otp-attempts-warning span').html(''); 491 492 // Disable user to send and verify OTP 493 disableSendOtp = true; 494 disableVerifyOtp = true; 495 496 $('#sendEmailOtp').prop('disabled', true); 497 $('#sendMobileOtp').prop('disabled', true); 498 $('#verifyOtp2fa').prop('disabled', true); 499 500 const otpStatusSpan = $('.otp-status'); 501 otpStatusSpan.removeClass('red'); 502 $('.otp-status-container').addClass('status-container-card'); 503 504 otpStatusSpan.html(` 505 Your sign in has been blocked because you have exceeded the number of OTP requests available to you at this time 506 <br> 507 Please try signing in after 508 <span class="otp-requests-lock-timer"></span> 509 `) 510 } 511 512 function tooManyIncorrectAttemptsLock() { 513 const contactSupportAnchor = '<a href="mailto:[email protected]">[email protected]</a>'; 514 515 // Clear other error message spans 516 $('.otp-status span').html(''); 517 $('.otp-verify-info span').html(''); 518 519 // Remove sign in button and footer 520 $('#twoFactorForm2fa .card-body .btn-wrapper').remove(); 521 $('.login-section .footer-text').remove(); 522 523 disableSendOtp = true; 524 disableVerifyOtp = true; 525 526 $('#sendEmailOtp').prop('disabled', true); 527 $('#sendMobileOtp').prop('disabled', true); 528 529 // Update locked out status card text and unhide it 530 $('.locked-status-card .card-body p:nth-child(1)').html(` 531 You are locked out of your account because you have made too many incorrect login attempts 532 `) 533 534 $('.locked-status-card .card-body p:nth-child(2)').html(` 535 Please contact at ${contactSupportAnchor} to unblock your account 536 `) 537 538 $('.locked-status-card').removeClass('d-none'); 539 } 540 541 function processKeyUp(e) { 542 const keyCode = e.keyCode; 543 const index = $('.digit-group').find('input').index(this); 544 const length = (this.id === 'userNumber' ? this.innerText.length : this.value.length); 545 546 if ($(this).parent().hasClass('digit-group') && [8, 37, 46].includes(keyCode)) { 547 if (index != 0) { 548 $('.digit-group').find('input').eq(index - 1).focus(); 549 } 550 } else if ($(this).parent().hasClass('digit-group') && (length == 1 || keyCode == 39)) { 551 $(this).next('input').focus(); 552 } 553 554 } 555 556 function processKeyDown(e) {
557 const key = e.key; 558 const keyCode = e.keyCode; 559 const acceptedKeyCodes = [8, 37, 39, 46]; // Accepted characters apart from number inputs 560 this.id === 'userNumber' ? acceptedKeyCodes.push(61) : ''; // accept '+' key if input field is for mobile 561 const isValid = (keyCode >= 48 && keyCode <= 57) || (keyCode >= 96 && keyCode <= 105) || acceptedKeyCodes.includes(keyCode) 562 563 const accepted = acceptedKeyCodes.includes(keyCode); 564 565 if (isNaN(key) && !accepted || keyCode == 32) { 566 e.preventDefault(); 567 } 568 569 } 570 571 function createRequestLockTimer(resetTime) { 572 let timeDelta = resetTime - new Date(); 573 574 if (timeDelta > 0) { 575 let timer = setInterval(function () { 576 const seconds = Math.floor(timeDelta / 1000); 577 const minutes = Math.floor(seconds / 60); 578 579 if (minutes >= 1) { 580 $('.otp-requests-lock-timer').text(`${minutes} minute(s)`); 581 } else { 582 $('.otp-requests-lock-timer').text(`${seconds} second(s)`); 583 } 584 585 586 timeDelta -= 1000; 587 588 if (seconds <= 1) { 589 clearInterval(timer); 590 window.location.reload(); 591 } 592 }, 1000); 593 } 594 } 595 596 function addTwoFactorEventListeners() { 597 // Send OTP buttons 598 $('#sendEmailOtp').on('click', sendEmailOtp); 599 $('#sendMobileOtp').on('click', sendMobileOtp); 600 601 // Verify OTP/ Sign in button 602 $('#verifyOtp2fa').on('click', verifyOtp); 603 604 // Handle keyup and keydown on otp input fields 605 $('.digit-group input').on('keyup', processKeyUp); 606 $('.digit-group input').on('keydown', processKeyDown); 607 } 608 609})
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.