1const nameCheck = /^[-_a-zA-Z0-9]{4,22}$/; 2const tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/; 3 4// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager 5document.addEventListener('submit', function (event) { 6 generateCsrfToken(event.target); 7}, true); 8 9// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie 10// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked 11document.addEventListener('turbo:submit-start', function (event) { 12 const h = generateCsrfHeaders(event.detail.formSubmission.formElement); 13 Object.keys(h).map(function (k) { 14 event.detail.formSubmission.fetchRequest.headers[k] = h[k]; 15 }); 16}); 17 18// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted 19document.addEventListener('turbo:submit-end', function (event) { 20 removeCsrfToken(event.detail.formSubmission.formElement); 21}); 22 23export function generateCsrfToken (formElement) { 24 const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); 25 26 if (!csrfField) { 27 return; 28 } 29 30 let csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); 31 let csrfToken = csrfField.value; 32 33 if (!csrfCookie && nameCheck.test(csrfToken)) { 34 csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken); 35 csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18)))); 36 } 37 csrfField.dispatchEvent(new Event('change', { bubbles: true })); 38 39 if (csrfCookie && tokenCheck.test(csrfToken)) { 40 const cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict'; 41 document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; 42 } 43} 44 45export function generateCsrfHeaders (formElement) { 46 const headers = {}; 47 const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); 48 49 if (!csrfField) { 50 return headers; 51 } 52 53 const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); 54 55 if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { 56 headers[csrfCookie] = csrfField.value; 57 } 58 59 return headers; 60} 61 62export function removeCsrfToken (formElement) { 63 const csrfField = formElement.querySelector('input[data-controller="csrf-protection"], input[name="_csrf_token"]'); 64 65 if (!csrfField) { 66 return; 67 } 68 69 const csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); 70 71 if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { 72 const cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0'; 73 74 document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; 75 } 76} 77 78/* stimulusFetch: 'lazy' */ 79export default 'csrf-protection-controller';
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.