v0.1

SecurityConditional Visibility
#156 - Encrypt Sensitive Data Before Sending to Memberstack
This script protects sensitive user data by encrypting it in the browser before it’s sent to Memberstack.
Limit failed login attempts and trigger a timed security check to prevent brute force attacks.
Watch the video for step-by-step implementation instructions
<!-- 💙 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;
// Don string'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>More scripts in Security