github.com/letsencrypt/boulder@v0.20251208.0/sfe/static/overriderequest.js (about)

     1  const form = document.getElementById('override-form');
     2  const RATE_LIMIT = form.dataset.rateLimit;
     3  const VALIDATE_FIELD_PATH = form.dataset.validateFieldPath;
     4  const SUBMIT_REQUEST_PATH = form.dataset.submitRequestPath;
     5  const AUTO_APPROVED_SUCCESS_PATH = form.dataset.autoApprovedSuccessPath;
     6  const REQUEST_SUBMITTED_SUCCESS_PATH = form.dataset.RequestSubmittedSuccessPath;
     7  
     8  const ERR_REQUIRED = "This field is required.";
     9  const ERR_VALIDATE = "Unable to validate this field due to timeout, please try again.";
    10  const ERR_SUBMIT = "Submission failed, please try again.";
    11  const ERR_TIMEOUT = "Request timed out, please check your connection and try again.";
    12  
    13  const SUBMIT_TIMEOUT_MS = 10000;
    14  const FIELDS_SELECTOR = "input, select, textarea";
    15  const FIELD_STATES = {};
    16  
    17  const debounce = (callback, delay) => {
    18      let timerId;
    19      return (...args) => {
    20          clearTimeout(timerId);
    21          timerId = setTimeout(() => callback(...args), delay);
    22      };
    23  };
    24  
    25  const markFieldInvalid = (field, msg) => {
    26      field.classList.add("invalid");
    27      field.classList.remove("valid");
    28      field.closest(".form-field").querySelector(".error-message").textContent = msg;
    29      FIELD_STATES[field.name] = false;
    30      updateSubmitButtonState();
    31  };
    32  
    33  const markFieldValid = (field) => {
    34      field.classList.remove("invalid");
    35      field.classList.add("valid");
    36      field.closest(".form-field").querySelector(".error-message").textContent = "";
    37      FIELD_STATES[field.name] = true;
    38      updateSubmitButtonState();
    39  };
    40  
    41  const showBanner = m => {
    42      const b = document.getElementById("form-error-banner");
    43      b.textContent = m;
    44      b.style.display = "block";
    45  };
    46  
    47  const hideBanner = () => {
    48      document.getElementById("form-error-banner").style.display = "none";
    49  };
    50  
    51  const updateSubmitButtonState = () => {
    52      const btn = document.getElementById("submit-button");
    53      const allValid = Object.values(FIELD_STATES).every(Boolean);
    54      btn.disabled = !allValid;
    55      btn.classList.toggle("btn-disabled", !allValid);
    56  };
    57  
    58  const validateFieldContents = async (field) => {
    59      const val = field.type === "checkbox" ? String(field.checked) : field.value.trim();
    60  
    61      if (field.required && ((field.type === "checkbox" && !field.checked) || (field.type !== "checkbox" && !val))) {
    62          markFieldInvalid(field, ERR_REQUIRED);
    63          return;
    64      }
    65  
    66      try {
    67          const r = await fetch(VALIDATE_FIELD_PATH, {
    68              method: "POST",
    69              headers: { "Content-Type": "application/json" },
    70              body: JSON.stringify({
    71                  rateLimit: RATE_LIMIT,
    72                  field: field.name,
    73                  value: val
    74              })
    75          });
    76          const res = await r.json();
    77          res.valid ? markFieldValid(field) : markFieldInvalid(field, res.error);
    78      } catch {
    79          markFieldInvalid(field, ERR_VALIDATE);
    80      }
    81  };
    82  
    83  const submitForm = async (e) => {
    84      e.preventDefault();
    85      hideBanner();
    86  
    87      if (!Object.values(FIELD_STATES).every(Boolean)) return;
    88  
    89      const payload = {
    90          rateLimit: RATE_LIMIT,
    91          fields: {}
    92      };
    93      document.querySelectorAll(FIELDS_SELECTOR).forEach(field => {
    94          payload.fields[field.name] = field.type === "checkbox" ? String(field.checked) : field.value.trim();
    95      });
    96  
    97      const ctl = new AbortController();
    98      const t = setTimeout(() => ctl.abort(), SUBMIT_TIMEOUT_MS);
    99      try {
   100          const r = await fetch(SUBMIT_REQUEST_PATH, {
   101              method: "POST",
   102              headers: { "Content-Type": "application/json" },
   103              body: JSON.stringify(payload),
   104              signal: ctl.signal
   105          });
   106          clearTimeout(t);
   107  
   108          if (!r.ok) {
   109              const d = await r.json().catch(() => ({}));
   110              showBanner(d.error || ERR_SUBMIT);
   111              return;
   112          }
   113          
   114          if (r.status === 201) {
   115              window.location.replace(AUTO_APPROVED_SUCCESS_PATH);
   116          } else if (r.status === 202) {
   117              window.location.replace(REQUEST_SUBMITTED_SUCCESS_PATH);
   118          }
   119      } catch {
   120          showBanner(ERR_TIMEOUT);
   121      }
   122  };
   123  
   124  document.addEventListener("DOMContentLoaded", () => {
   125      document.getElementById("override-form").addEventListener("submit", submitForm);
   126  
   127      document.querySelectorAll(FIELDS_SELECTOR).forEach(field => {
   128          if (field.tagName === "INPUT" && field.type !== "checkbox") field.setAttribute("autocomplete", "off");
   129          FIELD_STATES[field.name] = false;
   130          const handler = () => validateFieldContents(field);
   131          field.addEventListener(field.type === "checkbox" ? "change" : "input", debounce(handler, 300));
   132      });
   133      updateSubmitButtonState();
   134  });