#225 - Force Password Change After X Days

Force members to update their password after a configurable number of days

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

184 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #225 v0.1 💙 FORCE PASSWORD CHANGE AFTER X DAYS -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  var CONFIG = {
    maxAgeDays: 90,
    snoozeDays: 1,
    maxSnoozes: 3,
    jsonKey: "password_policy",
    modalDisplay: "flex",
    allowSnooze: true
  };

  var memberstack = window.$memberstackDom;
  if (!memberstack) {
    console.warn("Memberscript #number225: Memberstack not found");
    return;
  }

  // ─── GET MEMBER ───

  var member;
  try {
    var memberResult = await memberstack.getCurrentMember();
    member = memberResult?.data || memberResult;
  } catch (err) {
    console.warn("Memberscript #number225: Could not get member", err);
    return;
  }
  if (!member || !member.id) return;

  var memberJSON = {};
  try {
    var jsonResult = await memberstack.getMemberJSON();
    memberJSON = jsonResult?.data || {};
  } catch (e) {}

  // ─── DOM REFERENCES ───

  var modal = document.querySelector('[data-ms-pwexpiry="modal"]');
  if (!modal) return;

  var maxAgeDays   = parseInt(modal.getAttribute("ms-pwexpiry-days"))         || CONFIG.maxAgeDays;
  var snoozeDays   = parseInt(modal.getAttribute("ms-pwexpiry-snooze-days")) || CONFIG.snoozeDays;
  var maxSnoozes   = parseInt(modal.getAttribute("ms-pwexpiry-max-snoozes")) || CONFIG.maxSnoozes;
  var jsonKey       = modal.getAttribute("ms-pwexpiry-field")       || CONFIG.jsonKey;
  var modalDisplay  = modal.getAttribute("ms-pwexpiry-display")     || CONFIG.modalDisplay;
  var allowSnooze   = modal.getAttribute("ms-pwexpiry-allow-snooze");
  allowSnooze = allowSnooze !== null ? allowSnooze !== "keywordfalse" : CONFIG.allowSnooze;

  var daysExpiredEl   = document.querySelector('[data-ms-pwexpiry="days-expired"]');
  var lastChangedEl   = document.querySelector('[data-ms-pwexpiry="last-changed"]');
  var maxAgeDaysEl    = document.querySelector('[data-ms-pwexpiry="max-age-days"]');
  var snoozeCountEl   = document.querySelector('[data-ms-pwexpiry="snoozes-remaining"]');
  var changePwBtn     = document.querySelectorAll('[data-ms-pwexpiry="change-password"]');
  var snoozeBtns      = document.querySelectorAll('[data-ms-pwexpiry="snooze"]');
  var successView     = modal.querySelector('[data-ms-pwexpiry="success"]');

  modal.style.display = "none";
  if (successView) successView.style.display = "none";

  // ─── TRACKING HELPERS ───

  function getTracking() {
    var d = memberJSON[jsonKey];
    if (!d || typeof d !== "object") {
      return { last_changed: null, snooze_until: null, snooze_count: 0 };
    }
    return d;
  }

  async function saveTracking(data) {
    memberJSON[jsonKey] = data;
    try {
      await memberstack.updateMemberJSON({ json: memberJSON });
    } catch (e) {
      console.warn("Memberscript #number225: Could not save tracking data", e);
    }
  }

  function daysBetween(dateA, dateB) {
    return Math.floor((dateB - dateA) / 86400000);
  }

  function formatDate(isoStr) {
    var d = new Date(isoStr);
    var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
    return months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
  }

  // ─── SHOW MODAL ───

  function showModal(tracking) {
    var daysSince = daysBetween(new Date(tracking.last_changed), new Date());
    var snoozesLeft = Math.max(0, maxSnoozes - (tracking.snooze_count || 0));
    var canSnooze = allowSnooze && snoozesLeft > 0;

    if (daysExpiredEl) daysExpiredEl.textContent = daysSince;
    if (lastChangedEl) lastChangedEl.textContent = formatDate(tracking.last_changed);
    if (maxAgeDaysEl) maxAgeDaysEl.textContent = maxAgeDays;
    if (snoozeCountEl) snoozeCountEl.textContent = snoozesLeft;

    snoozeBtns.forEach(function(btn) {
      btn.style.display = canSnooze ? "" : "none";
    });

    modal.style.display = modalDisplay;
  }

  // ─── PASSWORD CHANGED ───

  async function markPasswordChanged(tracking) {
    tracking.last_changed = new Date().toISOString();
    tracking.snooze_until = null;
    tracking.snooze_count = 0;
    await saveTracking(tracking);

    if (successView) {
      successView.style.display = "";
      setTimeout(function() {
        modal.style.display = "none";
        if (successView) successView.style.display = "none";
      }, 3000);
    } else {
      modal.style.display = "none";
    }
  }

  // ─── SNOOZE ───

  async function snooze(tracking) {
    tracking.snooze_count = (tracking.snooze_count || 0) + 1;
    var until = new Date();
    until.setDate(until.getDate() + snoozeDays);
    tracking.snooze_until = until.toISOString();
    await saveTracking(tracking);
    modal.style.display = "none";
  }

  // ─── MAIN LOGIC ───

  var tracking = getTracking();
  var now = new Date();

  if (!tracking.last_changed) {
    tracking.last_changed = now.toISOString();
    tracking.snooze_count = 0;
    await saveTracking(tracking);
    return;
  }

  var lastChangedDate = new Date(tracking.last_changed);
  var daysSinceChange = daysBetween(lastChangedDate, now);

  if (daysSinceChange < maxAgeDays) return;

  if (tracking.snooze_until) {
    var snoozeExpiry = new Date(tracking.snooze_until);
    if (now < snoozeExpiry) return;
  }

  showModal(tracking);

  // ─── EVENT HANDLERS ───

  changePwBtn.forEach(function(btn) {
    btn.addEventListener("click", async function(e) {
      e.preventDefault();
      try {
        await memberstack.openModal("PROFILE", { defaultTab: "security" });
        await markPasswordChanged(tracking);
      } catch (err) {
        console.warn("Memberscript #number225: Could not open profile modal", err);
      }
    });
  });

  snoozeBtns.forEach(function(btn) {
    btn.addEventListener("click", function(e) {
      e.preventDefault();
      snooze(tracking);
    });
  });
});
</script>

Script Info

Versionv0.1
PublishedApr 21, 2026
Last UpdatedApr 21, 2026

Need Help?

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

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in JSON