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 });