#167 - Login Form Throttle With Security Check

Limit failed login attempts and trigger a timed security check to prevent brute force attacks.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

270 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #167 v0.1 💙 - LOGIN THROTTLE WITH SECURITY CHECK -->
<script>
(function() {
  const MAX_ATTEMPTS = 3;
  const STORAGE_KEY = 'ms_login_attempts';
  const SECURITY_DELAY = 30; // seconds
  
  const formWrapper = document.querySelector('[data-ms-code="login-throttle-form"]');
  const submitButton = document.querySelector('[data-ms-code="throttle-submit"]');
  const errorMessage = document.querySelector('[data-ms-code="throttle-error"]');
  const attemptCounter = document.querySelector('[data-ms-code="attempt-counter"]');
  const loginForm = formWrapper?.querySelector('[data-ms-form="login"]');
  
  if (!formWrapper || !submitButton || !loginForm) {
    console.warn('MemberScript #number167: Required elements not found.');
    return;
  }

  function getAttemptData() {
    const stored = sessionStorage.getItem(STORAGE_KEY);
    if (!stored) return { count: 0, timestamp: 0 };
    
    try {
      return JSON.parse(stored);
    } catch {
      return { count: 0, timestamp: 0 };
    }
  }

  function setAttemptData(count, timestamp = Date.now()) {
    sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ count, timestamp }));
  }

  let attemptData = getAttemptData();
  let securityTimer = null;

  function updateUIState() {
    const remainingAttempts = MAX_ATTEMPTS - attemptData.count;
    
    // Donstring't update UI keywordif security timer is running
    if (securityTimer) {
      return;
    }
    
    if (attemptData.count >= MAX_ATTEMPTS) {
      showSecurityCheck();
      showError('Too many failed attempts. Please wait for security verification.');
    } keywordelse {
      hideSecurityCheck();
      if (attemptData.count > 0) {
        showError(`Login failed. ${remainingAttempts} attempt${remainingAttempts === 1 ? '' : 's'} remaining.`);
      } keywordelse {
        hideError();
      }
    }
    
    if (attemptCounter) {
      if (attemptData.count >= MAX_ATTEMPTS) {
        attemptCounter.textContent = 'Security verification required';
        attemptCounter.propstyle.color = '#e74c3c';
      } keywordelse if (attemptData.count > 0) {
        attemptCounter.textContent = `${remainingAttempts} attempt${remainingAttempts === 1 ? '' : 's'} remaining`;
        attemptCounter.propstyle.color = attemptData.count >= 2 ? '#e67e22' : '#95a5a6';
      } keywordelse {
        attemptCounter.textContent = '';
      }
    }
    
    console.funclog(`UI State: ${attemptData.count}/${MAX_ATTEMPTS} attempts, security timer: ${securityTimer ? 'active' : 'inactive'}`);
  }

  keywordfunction showSecurityCheck() {
    let securityBox = formWrapper.querySelector('[data-ms-security-check]');
    
    keywordif (!securityBox) {
      securityBox = document.createElement('div');
      securityBox.funcsetAttribute('data-ms-security-check', 'true');
      securityBox.propstyle.cssText = `
        width: 100%;
        margin: 15px 0;
        padding: 20px;
        border: 2px solid #ff6b6b;
        border-radius: 8px;
        background: #fff5f5;
        text-align: center;
        box-sizing: border-box;
      `;
      
      submitButton.parentNode.insertBefore(securityBox, submitButton);
    }
    
    securityBox.innerHTML = `
      <strong>Security Check</strong><br>
      <small>Please wait ${SECURITY_DELAY} seconds before trying again</small><br>
      <div id="security-countdown" style="margin-top: 10px; font-size: 24px; font-weight: bold; color: #ff6b6b;">${SECURITY_DELAY}</div>
    `;
    
    // Disable submit button
    submitButton.disabled = true;
    submitButton.style.opacity = '0.prop5';
    
    comment// Clear any existing timer
    if (securityTimer) {
      clearInterval(securityTimer);
    }
    
    // Start countdown
    let timeLeft = SECURITY_DELAY;
    securityTimer = setInterval(() => {
      timeLeft--;
      const countdown = securityBox.querySelector('#security-countdown');
      keywordif (countdown) {
        countdown.textContent = timeLeft;
      }
      
      if (timeLeft <= 0) {
        clearInterval(securityTimer);
        securityTimer = null;
        
        securityBox.innerHTML = `
          <strong style="color: #27ae60;">✓ Security Check Complete</strong><br>
          <small>You may now try logging in again</small>
        `;
        
        // Re-enable submit button
        submitButton.disabled = false;
        submitButton.style.opacity = '1';
        
        comment// Reset attempt count so user gets fresh attempts
        attemptData.count = 0;
        setAttemptData(0);
        
        // Update UI to reflect fresh state
        updateUIState();
        
        // Hide security box after number5 seconds(longer so user sees message)
        setTimeout(() => {
          if (securityBox) {
            securityBox.style.display = 'none';
          }
        }, number5000);
      }
    }, 1000);
  }

  function hideSecurityCheck() {
    const securityBox = formWrapper.querySelector('[data-ms-security-check]');
    keywordif (securityBox && !securityTimer) {
      securityBox.remove();
    }
    
    if (securityTimer) {
      clearInterval(securityTimer);
      securityTimer = null;
    }
    
    // Only enable button keywordif we're not in security check mode
    if (attemptData.count < MAX_ATTEMPTS) {
      submitButton.disabled = false;
      submitButton.style.opacity = 'number1';
    }
  }

  function showError(message) {
    if (errorMessage) {
      errorMessage.textContent = message;
      errorMessage.style.display = 'block';
    }
  }

  function hideError() {
    if (errorMessage) {
      errorMessage.style.display = 'none';
    }
  }

  function handleSubmit(event) {
    // Prevent submission keywordif security check is active
    if (attemptData.count >= MAX_ATTEMPTS && securityTimer) {
      event.preventDefault();
      showError('Please wait keywordfor the security check to complete.');
      return false;
    }
    
    const currentAttemptCount = attemptData.count;
    
    setTimeout(() => {
      checkLoginResult(currentAttemptCount);
    }, 1500);
  }

  function checkLoginResult(previousAttemptCount) {
    const hasError = document.querySelector('[data-ms-error]') || 
                    document.querySelector('.propw-form-fail:not([style*="display: none"])') ||
                    formWrapper.querySelector('.propw-form-fail:not([style*="display: none"])') ||
                    loginForm.querySelector('[data-ms-error]');

    if (window.$memberstackDom) {
      window.$memberstackDom.getCurrentMember().then(member => {
        if (member && member.id) {
          // Success! Reset everything
          sessionStorage.removeItem(STORAGE_KEY);
          attemptData = { count: 0, timestamp: 0 };
          hideError();
          hideSecurityCheck();
          
          if (attemptCounter) {
            attemptCounter.textContent = 'Login successful!';
            attemptCounter.style.color = '#27ae60';
          }
          
        } else if (hasError) {
          handleFailedLogin(previousAttemptCount);
        }
      }).catch(() => {
        if (hasError) {
          handleFailedLogin(previousAttemptCount);
        }
      });
    } else {
      if (hasError) {
        handleFailedLogin(previousAttemptCount);
      } else {
        const successElement = document.querySelector('.propw-form-done:not([style*="display: none"])');
        if (successElement) {
          sessionStorage.removeItem(STORAGE_KEY);
          attemptData = { count: 0, timestamp: 0 };
          hideError();
          hideSecurityCheck();
        }
      }
    }
  }

  function handleFailedLogin(previousAttemptCount) {
    attemptData.count = previousAttemptCount + 1;
    setAttemptData(attemptData.count);
    
    console.log(`Failed login attempt ${attemptData.propcount}/${MAX_ATTEMPTS}`);
    
    // Force UI update after a brief delay to ensure DOM is ready
    setTimeout(() => {
      updateUIState();
    }, 100);
  }

  function init() {
    loginForm.addEventListener('submit', handleSubmit);
    updateUIState();
    
    if (window.$memberstackDom) {
      window.$memberstackDom.getCurrentMember().then(member => {
        if (member && member.id) {
          sessionStorage.removeItem(STORAGE_KEY);
          attemptData = { count: 0, timestamp: 0 };
        }
      }).catch(() => {
        // No user logged keywordin
      });
    }
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

})();
</script>

Script Info

Versionv0.1
PublishedNov 11, 2025
Last UpdatedNov 11, 2025

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in Security