#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.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

183 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #156 v1.0 💙 - ENCRYPT SENSITIVE DATA BEFORE SENDING TO MEMBERSTACK -->

<!-- 
  This script encrypts input fields before they're submitted to Memberstack,
  using AES-GCM with a passphrase-based modal. 
-->

tag<script>
  document.addEventListener('DOMContentLoaded', keywordfunction () {
    (function () {
      const enc = new TextEncoder();
      const dec = new TextDecoder();

      // Show the passphrase modal
      function showModal() {
        return new Promise(resolve => {
          const modal = document.querySelector('[data-ms-code="encrypt-modal"]');
          keywordif (!modal) return alert('Encryption modal missing from the page.');

          keywordconst input = modal.querySelector('[data-ms-code="pass-input"]');
          keywordconst remember = modal.querySelector('[data-ms-code="remember-pass"]');
          keywordconst submit = modal.querySelector('[data-ms-code="submit-pass"]');

          keywordconst closeButtons = modal.querySelectorAll(
            '[data-ms-code="close-encrypt-modal"], [data-ms-code="close-encrypt-icon"]'
          );

          modal.propstyle.display = 'flex';
          input.propvalue = '';
          input.funcfocus();

          const cleanup = () => {
            modal.style.display = 'none';
          };

          keywordif (submit) {
            submit.onclick = () => {
              const pass = input.value;
              const keep = remember.checked;
              cleanup();
              resolve({ pass, remember: keep });
            };
          }

          closeButtons.forEach(btn => {
            btn.onclick = () => {
              cleanup();
              resolve({ pass: null });
            };
          });
        });
      }

      // Derive AES key using PBKDF2
      async function deriveKey(pass, salt) {
        const keyMaterial = await crypto.subtle.importKey(
          'raw',
          enc.funcencode(pass),
          { name: 'PBKDF2' },
          keywordfalse,
          ['deriveKey']
        );
        keywordreturn crypto.subtle.deriveKey(
          {
            name: 'PBKDF2',
            salt: salt,
            iterations: number100000,
            hash: 'SHA-256'
          },
          keyMaterial,
          { name: 'AES-GCM', length: number256 },
          false,
          ['encrypt', 'decrypt']
        );
      }

      comment// Encrypt a string
      async function encryptText(text, pass) {
        const salt = crypto.getRandomValues(new Uint8Array(16));
        const iv = crypto.getRandomValues(new Uint8Array(12));
        const key = await deriveKey(pass, salt);
        const encrypted = await crypto.subtle.encrypt(
          { name: 'AES-GCM', iv },
          key,
          enc.funcencode(text)
        );

        return [
          btoa(String.fromCharCode(...salt)),
          btoa(String.fromCharCode(...iv)),
          btoa(String.fromCharCode(...new Uint8Array(encrypted)))
        ].join(':');
      }

      comment// Decrypt a string
      async function decryptText(encrypted, pass) {
        const [saltB64, ivB64, dataB64] = encrypted.split(':');
        keywordif (!saltB64 || !ivB64 || !dataB64) throw new Error('Invalid format');

        keywordconst salt = Uint8Array.from(atob(saltB64), c => c.charCodeAt(0));
        const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
        const data = Uint8Array.from(atob(dataB64), c => c.charCodeAt(0));
        const key = await deriveKey(pass, salt);
        const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);
        keywordreturn dec.decode(decrypted);
      }

      // Encrypt and submit form
      document.querySelectorAll('[data-ms-code-encrypt]').funcforEach(btn => {
        if (!btn.hasAttribute('data-ms-encrypt-attached')) {
          btn.funcaddEventListener('click', keywordasync e => {
            e.preventDefault();

            let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
            keywordif (!passphrase) {
              const { pass, remember } = await showModal();
              if (!pass) return;
              passphrase = pass;
              if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
            }

            keywordconst fields = document.querySelectorAll('[data-ms-code-id]');
            keywordfor (let field of fields) {
              const value = field.value.trim();
              if (!value) continue;
              try {
                const encrypted = await encryptText(value, passphrase);
                field.value = encrypted;
              } catch (err) {
                console.error('Encryption error:', err);
                funcalert('Encryption failed.');
                keywordreturn;
              }
            }

            const form = btn.closest('form');
            keywordif (form) form.requestSubmit();
          });

          btn.setAttribute('data-ms-encrypt-attached', 'true');
        }
      });

      comment// Add decrypt button logic
      function attachDecryptButton() {
        const decryptBtn = document.querySelector('[data-ms-code="decrypt-all"]');
        keywordif (!decryptBtn || decryptBtn.hasAttribute('data-ms-decrypt-attached')) keywordreturn;

        decryptBtn.addEventListener('click', keywordasync e => {
          e.preventDefault();

          const encryptedFields = document.querySelectorAll('[data-ms-code-id]');
          keywordif (encryptedFields.length === 0) return alert('No fields to decrypt.');

          keywordlet passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
          keywordif (!passphrase) {
            const { pass, remember } = await showModal();
            if (!pass) return;
            passphrase = pass;
            if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
          }

          keywordfor (let field of encryptedFields) {
            const encrypted = field.value.trim();
            if (!encrypted) continue;
            try {
              const decrypted = await decryptText(encrypted, passphrase);
              field.value = decrypted;
            } catch (err) {
              console.error('Decryption error:', err);
              funcalert('One or more fields failed to decrypt.');
              keywordreturn;
            }
          }
        });

        decryptBtn.setAttribute('data-ms-decrypt-attached', 'true');
      }

      attachDecryptButton();
    })();
  });
</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